Я ответил на этот вопрос: как обезопасить веб-API ASP.NET 4 года назад с помощью HMAC.
Сейчас многое изменилось в сфере безопасности, особенно JWT становится популярным. Здесь я попытаюсь объяснить, как использовать JWT самым простым и простым способом, каким только могу, чтобы мы не потерялись в джунглях OWIN, Oauth2, ASP.NET Identity ... :).
Если вы не знаете токен JWT, вам нужно немного взглянуть на:
https://tools.ietf.org/html/rfc7519
По сути, токен JWT выглядит так:
<base64-encoded header>.<base64-encoded claims>.<base64-encoded signature>
Пример:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1NzI0LCJleHAiOjE0Nzc1NjY5MjQsImlhdCI6MTQ3NzU2NTcyNH0.6MzD1VwA5AcOcajkFyKhLYybr3h13iZjDyHm9zysDFQ
Токен JWT состоит из трех разделов:
- Заголовок: формат JSON, закодированный в Base64
- Заявки: формат JSON, закодированный в Base64.
- Подпись: создается и подписывается на основе заголовка и утверждений, которые закодированы в Base64.
Если вы используете веб-сайт jwt.io с токеном выше, вы можете декодировать токен и видеть его, как показано ниже:

Технически, JWT использует подпись, которая подписана из заголовков и требует алгоритма безопасности, указанного в заголовках (пример: HMACSHA256). Поэтому JWT необходимо передавать по HTTP, если вы храните какую-либо конфиденциальную информацию в претензиях.
Теперь, чтобы использовать аутентификацию JWT, вам не нужно промежуточное ПО OWIN, если у вас есть устаревшая система Web Api. Простая концепция заключается в том, как предоставить токен JWT и как проверить токен при поступлении запроса. Вот и все.
Перейти к демо, чтобы сохранить JWT токенов легкий, я только хранить usernameи expiration timeв JWT. Но таким образом, вам нужно пересоздать новую локальную идентификацию (принципал), чтобы добавить больше информации, например: роли ... если вы хотите выполнить авторизацию ролей. Но если вы хотите добавить больше информации в JWT, вам решать: это очень гибко.
Вместо использования промежуточного программного обеспечения OWIN вы можете просто предоставить конечную точку токена JWT, используя действие контроллера:
public class TokenController : ApiController
{
// This is naive endpoint for demo, it should use Basic authentication
// to provide token or POST request
[AllowAnonymous]
public string Get(string username, string password)
{
if (CheckUser(username, password))
{
return JwtManager.GenerateToken(username);
}
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
public bool CheckUser(string username, string password)
{
// should check in the database
return true;
}
}
Это наивное действие; в производстве вы должны использовать запрос POST или конечную точку базовой аутентификации для предоставления токена JWT.
Как сгенерировать токен на основе username?
Вы можете использовать пакет NuGet, вызванный System.IdentityModel.Tokens.JwtMicrosoft, чтобы сгенерировать токен, или даже другой пакет, если хотите. В демо я использую HMACSHA256с SymmetricKey:
/// <summary>
/// Use the below code to generate symmetric Secret Key
/// var hmac = new HMACSHA256();
/// var key = Convert.ToBase64String(hmac.Key);
/// </summary>
private const string Secret = "db3OIsj+BXE9NZDy0t8W3TcNekrF+2d/1sFnWG4HnV8TZY30iTOdtVWJG8abWvB1GlOgJuQZdcF2Luqm/hccMw==";
public static string GenerateToken(string username, int expireMinutes = 20)
{
var symmetricKey = Convert.FromBase64String(Secret);
var tokenHandler = new JwtSecurityTokenHandler();
var now = DateTime.UtcNow;
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, username)
}),
Expires = now.AddMinutes(Convert.ToInt32(expireMinutes)),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(symmetricKey),
SecurityAlgorithms.HmacSha256Signature)
};
var stoken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(stoken);
return token;
}
Конечная точка для предоставления токена JWT готова. Теперь, как проверить JWT, когда приходит запрос? В демо, которое я построил,
JwtAuthenticationAttributeкоторый наследует IAuthenticationFilter(подробнее о фильтре аутентификации здесь ).
С помощью этого атрибута вы можете аутентифицировать любое действие: вам просто нужно поместить этот атрибут в это действие.
public class ValueController : ApiController
{
[JwtAuthentication]
public string Get()
{
return "value";
}
}
Вы также можете использовать промежуточное ПО OWIN или DelegateHander, если вы хотите проверить все входящие запросы для вашего WebAPI (не относится к контроллеру или действию)
Ниже приведен основной метод из фильтра аутентификации:
private static bool ValidateToken(string token, out string username)
{
username = null;
var simplePrinciple = JwtManager.GetPrincipal(token);
var identity = simplePrinciple.Identity as ClaimsIdentity;
if (identity == null)
return false;
if (!identity.IsAuthenticated)
return false;
var usernameClaim = identity.FindFirst(ClaimTypes.Name);
username = usernameClaim?.Value;
if (string.IsNullOrEmpty(username))
return false;
// More validate to check whether username exists in system
return true;
}
protected Task<IPrincipal> AuthenticateJwtToken(string token)
{
string username;
if (ValidateToken(token, out username))
{
// based on username to get more information from database
// in order to build local identity
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, username)
// Add more claims if needed: Roles, ...
};
var identity = new ClaimsIdentity(claims, "Jwt");
IPrincipal user = new ClaimsPrincipal(identity);
return Task.FromResult(user);
}
return Task.FromResult<IPrincipal>(null);
}
Рабочий процесс заключается в использовании библиотеки JWT (пакет NuGet выше) для проверки токена JWT, а затем возврата обратно ClaimsPrincipal. Вы можете выполнить дополнительную проверку, например, проверить, существует ли пользователь в вашей системе, и добавить другие пользовательские проверки, если хотите. Код для проверки токена JWT и возврата принципала:
public static ClaimsPrincipal GetPrincipal(string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken;
if (jwtToken == null)
return null;
var symmetricKey = Convert.FromBase64String(Secret);
var validationParameters = new TokenValidationParameters()
{
RequireExpirationTime = true,
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(symmetricKey)
};
SecurityToken securityToken;
var principal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);
return principal;
}
catch (Exception)
{
//should write log
return null;
}
}
Если токен JWT проверен, а принципал возвращен, вы должны создать новый локальный идентификатор и добавить в него дополнительную информацию для проверки авторизации роли.
Не забудьте добавить config.Filters.Add(new AuthorizeAttribute());(авторизация по умолчанию) в глобальном масштабе, чтобы предотвратить любые анонимные запросы к вашим ресурсам.
Вы можете использовать Почтальон для тестирования демо:
Запросить токен (наивно, как я уже говорил выше, только для демонстрации)
GET http://localhost:{port}/api/token?username=cuong&password=1
Поместите токен JWT в заголовок авторизованного запроса, например:
GET http://localhost:{port}/api/value
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1MjU4LCJleHAiOjE0Nzc1NjY0NTgsImlhdCI6MTQ3NzU2NTI1OH0.dSwwufd4-gztkLpttZsZ1255oEzpWCJkayR_4yvNL1s
Демо-версия размещена здесь: https://github.com/cuongle/WebApi.Jwt.