Я відповів на це питання: Як захистити веб-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