В ногу з часом: Використовуємо в JWT ASP.NET Core

У червні 2016 вийшов реліз ASP.Net Core 1.0 і тепер, якщо вас не лякає вік нового фреймворку, можна акуратно запустити микросервис в продакшн (адже використовують микросервисную архітектуру, чи не так?). Для того, щоб обмежити доступ до вашого микросервису для третіх осіб, необхідно зробити аутентифікацію, використовуючи досить поширений спосіб — токени. У статті під катом ми розповімо докладніше про те, як це зробити з допомогою JSON Web Token (JWT), а також про плюси і мінуси цього підходу.



Зазвичай токен — це випадково згенерований рядок, яка пов'язана з певним користувачем і для отримання даних (наприклад, id або email) необхідно зробити запит до бази даних (БД). Але, що якщо нам не потрібно робити зайвий запит до БД за даними користувача, а потрібно зберігати їх прямо всередині токена? Таке можливо за допомогою JWT. Розберемо, що таке JWT і створимо тестовий проект.

JWT — це підписаний об'єкт JSON, містить що-небудь корисне (наприклад, id користувача, його права/ролі), закодований у base64 і складається з трьох частин розділений точками
.
: Header, Payload, Signature і зазвичай виглядає так
aaaaaaa.bbbbbb.cccccc
. Більш повну інформацію можна знайти на jwt.io або RFC 7519.

  • Header — містить тип токена і назва хеширующего алгоритму:
    { "alg": "HS256", "typ": "JWT" }
  • Payload — об'єкт містить будь-які потрібні нам дані:
    { "email": "temp@jwt.ru", "user_id": "57dc51a3389b30fed1b13f91" }
  • Signature — служить для перевірки відправника, що він той, за кого себе видає, а так що собщение не було змінено. У випадку якщо ми використовуємо HMAC SHA256 алгоритм, створення підпису буде виглядати так:
    HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
Підготовка проекту
Як зазвичай почнемо з створення порожнього проекту. Після файл
project.json
додамо наступні залежності:

"Microsoft.AspNetCore.Authentication.JwtBearer": "1.0.0",
"Microsoft.AspNetCore.Mvc.Core": "1.0.0",
"Microsoft.AspNetCore.Mvc.Formatters.Json": "1.0.0"

Я використовую збірку
Microsoft.AspNetCore.Mvc.Core
замість
Microsoft.AspNetCore.Mvc
для того щоб не тягнути зайві (для нашого rest сервісу) залежності у вигляді Razor, TagHelper і т. д.

В ASP.NET Core стартова конфігурація проекту задається у файлі
Startup.cs
, трохи підправимо його:

public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseMvc().UseMvcWithDefaultRoute(); 
}
}

Налаштовуємо JWT
Відкриємо наш
Startup.cs
і допишемо наступне:

public class Startup {
public void ConfigureServices(IServiceCollection services) {

services.AddMvcCore()
.AddAuthorization();


services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) {

var key = Encoding.UTF8
.GetBytes("401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429090fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1");

var options = new JwtBearerOptions {

TokenValidationParameters = {
ValidIssuer = "ExampleIssuer",
ValidAudience = "ExampleAudience",
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
}
};

app.UseJwtBearerAuthentication(options);
app.UseMvcWithDefaultRoute();
}
}

У методі
ConfigureServices
все досить очевидно, ми додаємо використання сервісу авторизації, а так само реєструємо
HttpContextAccessor
. Для чого нам знадобилася явна реєстрація HttpContextAccessor, дізнаємося трохи пізніше. Перейдемо до методу
Configure
, в якому і відбувається основна настройка параметрів для валідації токена JWT. Зараз, нам найбільше цікаві три параметри:

IssuerSigningKey
— ключ, яким повинен бути підписаний наш токен. Для прикладу обрали SymmetricSecurityKey, але також можна вказати X509SecurityKey() або JsonWebKey, якщо ви володієте великою любов'ю до JSON.

ValidateIssuerSigningKey
— вказуємо, що будемо перевіряти ключ яким підписували токен JWT.
ValidateLifetime
— ставимо true, так як хочемо контролювати час життя сертифіката.

Створюємо маршрути
Тепер нам необхідно додати два методу: один для генерації токена, другий для перевірки JW — аутентифікації. Створимо простий контролер
HomeController.cs
:

[Route("/")]
public class HomeController {

private readonly IHttpContextAccessor _context;

public HomeController(IHttpContextAccessor context) {
_context = context;
}

[HttpGet("token")]
public dynamic GetToken() {
var handler = new JwtSecurityTokenHandler();

var sec = "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429090fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1";
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(sec));
var signingCredentials = new SigningCredentials(securityKey,SecurityAlgorithms.HmacSha256Signature);

var identity = new ClaimsIdentity(new GenericIdentity("temp@jwt.ru"), new[] { new Claim("user_id", "57dc51a3389b30fed1b13f91") });
var token = handler.CreateJwtSecurityToken(subject: identity,
signingCredentials: signingCredentials,
audience: "ExampleAudience",
issuer: "ExampleIssuer",
expires: DateTime.UtcNow.AddSeconds(42));
return handler.WriteToken(token);
}


[Authorize, HttpGet("secure")]
public dynamic Secret() {
var currentUser = _context.HttpContext.User;
return currentUser.Identity.Name;
}
}

Так як я використовую
AspNetCore.Mvc.Core
, то єдиний спосіб (хоча може бути і інший) дістатися до HttpContext — як раз через
IHttpContextAccessor
, який ми реєстрували раніше.

signingCredentials
— створюємо ключ яким підпишемо наш токен, він повинен бути однаковий з тим, який ми вказували в
Startup.cs
при налаштуванні JWT-параметрів.

identity
— створюємо наш
payload
. Безумовно, в реальному додатку дістанемо дані зі сховища, перед цим перевіривши їх, а зараз влаштуємо трохи хардкода.

При створенні токена, вказуємо його час життя:
expires: DateTime.UtcNow.AddSeconds(42)
, досить тривіально і гнучко.

Запустимо програму і виконаємо перший запит на отримання сертифіката:

curl -X GET "http://localhost:<your_port>/token"

У відповідь нам повернеться токен:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXvcj9.eyJ1bmlxdWVfbmFtZSI6InRlc3RAdGVzdc5ydsisinvzzxjfawqioii1n2rjntfhmzm4owizmgzlzdfimtnmoteilcjuymyioje0nzqymtu4mdasimv4cci6mtq3ndixntgznswiawf0ijoxndc0mje1odawlcjpc3mioijfegftcgxlsxnzdwvyiiwiyxvkijoirxhhbxbszuf1zgllbmnlin0.9NhOkoalaE70nIb-erH_waWx8rk6QJta5N19EiBLETQ

Спробуємо зробити запит на секретний рауса без сертифіката:

curl -X GET "http://localhost:<your_port>/secure"

У відповідь повернеться
401 Unauthorized
, що і очікувано.

Тепер зробимо запит з отриманим токеном:

curl -X GET -H "Authorization: Bearer token_should_be_here" "http://localhost:<your_port>/secure"

У відповіді буде міститися email, який ми передали в конструктор при створенні
GenericIdentity
.

Якщо ми почекаємо 42 секунди, а саме стільки живе наш токен, і спробуємо повторити попередній запит, то у відповідь отримаємо:
401 Unauthorized
та в хедері
WWW-Authenticate
значення
Bearer error="invalid_token", error_description="The token is expired"
, що повідомляє нам про минулому токені.

Замість висновку
Як і у будь-якого рішення, у JWT крім плюсів є і мінуси. Наприклад, якщо нам знадобиться відкликати токен, раніше ніж закінчиться час його життя, то ми можемо зробити це двома способами:

  • blacklist
    , який буде містити невалідні токени. Але, тепер нам все одно доведеться робити зайвий запит для перевірки нашого сертифіката.
  • Деякі пропонують використовувати два токена, один короткий, на 10 хвилин, і
    refresh
    — токен на більш тривалий час. Зовсім не гарне рішення на мій погляд.
Якщо ви можете дозволити у своєму проекті відгук всіх токенів разом, то можна сміливо використовувати JWT, а інакше виграш не дуже великий.

Ми зробили прототип проекту з використанням JWT, у якому ми можемо валідувати і створювати токени. Звичайно ж, в реальному проекті все буде трохи інакше, тому що формат статті не вистачить розповісти про всіх JWT-параметрах у ASP.NET Соге.

Про автора

Слава Бобик — інженер компанії Радарио, ентузіаст ASP.NET Core і OSS. Захоплюється розподіленими системами і стрибками з парашутом.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.