Я відповів на це питання: Як захистити веб-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.Jwtвід Microsoft, щоб генерувати маркер, або навіть інший пакет, якщо хочете. У демо - версії, я використовую 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());(авторизація за замовчуванням) у глобальному масштабі, щоб запобігти будь-якому анонімному запиту до ваших ресурсів.
Ви можете використовувати Postman для тестування демо-версії:
Запросіть маркер (наївно, як я вже згадував, лише для демонстрації):
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