paint-brush
Зачем вам нужен JWT в вашем проекте ASP.NET Core?by@igorlopushko
7,145
7,145

Зачем вам нужен JWT в вашем проекте ASP.NET Core?

Igor Lopushko16m2024/02/13
Read on Terminal Reader

История о том, как создать веб-API для генерации JWT и последующего использования его для авторизации в веб-API CRUD.
featured image - Зачем вам нужен JWT в вашем проекте ASP.NET Core?
Igor Lopushko HackerNoon profile picture
0-item

JWT означает JSON Web Token и представляет собой механизм авторизации, а не аутентификации. Итак, давайте разберемся, в чем разница между этими двумя.


Аутентификация — это механизм, позволяющий проверить, что пользователь именно тот, за кого себя выдает. Это процесс входа в систему, при котором пользователь предоставляет имя пользователя и пароль, а система проверяет их. Таким образом, аутентификация отвечает на вопрос: кто является пользователем?


Авторизация — это механизм, позволяющий проверить, какие права доступа имеет пользователь к определенному ресурсу. Это процесс предоставления пользователям некоторых ролей и набора разрешений, которые имеет конкретная роль. Итак, авторизация отвечает на вопрос: какие права имеет пользователь в системе?

Аутентификация против авторизации


Важно понимать, что аутентификация всегда стоит на первом месте, а авторизация — на втором. Другими словами, вы не сможете получить разрешение, пока не подтвердите свою личность. Но какие способы авторизации наиболее популярны? Существует два основных подхода к авторизации веб-приложения.

Сессии

Традиционный подход в Интернете для авторизации пользователей — сеанс на стороне сервера на основе файлов cookie. Процесс начинается, когда пользователь входит в систему и сервер аутентифицирует его. После этого сервер создает сеанс с идентификатором сеанса и сохраняет его где-то в памяти сервера. Сервер отправляет обратно идентификатор сеанса клиенту, и клиент сохраняет идентификатор сеанса в файлах cookie. Для каждого запроса клиент отправляет идентификатор сеанса как часть запроса, а сервер проверяет идентификатор сеанса в своей памяти и разрешения пользователя, связанные с этим сеансом.

Авторизация на основе сеанса

Токены

Другой популярный подход — использование токенов для авторизации. Процесс начинается аналогичным образом, когда пользователь вводит логин и пароли, а клиент отправляет запрос на вход на сервер. Вместо создания сеанса сервер генерирует токен, подписанный секретным токеном. Затем сервер отправляет токен обратно клиенту, и клиент должен сохранить его в локальном хранилище. Подобно подходу на основе сеанса, клиент должен отправлять токен на сервер для каждого запроса. Однако сервер не хранит никакой дополнительной информации о сеансе пользователя. Сервер должен убедиться, что токен не изменился с момента его создания и подписания секретным ключом.

Авторизация на основе токена

Сессия против токена

Подход авторизации на основе сеанса может быть уязвим для атаки, известной как подделка межсайтового запроса (CSRF). Это своего рода атака, когда злоумышленник указывает на сайт, на который он зашел, чтобы выполнить действия, которые он не собирался совершать, например, отправить платеж или изменить пароль.


Другое дело, что при использовании подхода авторизации на основе сеанса между клиентом и сервером создается сеанс с сохранением состояния. Проблема в том, что если клиент хочет получить доступ к разным серверам в рамках одного и того же приложения, эти серверы должны использовать общее состояние сеанса. В другом случае клиенту потребуется авторизоваться на каждом сервере, поскольку сессии будут разными.

Совместное использование состояния авторизации на основе сеанса


С другой стороны, подход авторизации на основе токенов не требует хранения данных сеанса на стороне сервера и может упростить авторизацию между несколькими серверами.


Однако токены все равно могут быть украдены злоумышленником, и аннулировать токены может быть сложно. Подробности и способы обработки аннулирования мы увидим далее в этой статье.

JWT

JSON Web Token (JWT) — это открытый стандарт, определяющий компактный и автономный способ безопасной передачи информации между сторонами в виде объекта JSON. Эту информацию можно проверить и ей можно доверять, поскольку она имеет цифровую подпись. JWT могут быть подписаны с использованием секрета (с помощью алгоритма HMAC ) или пары открытого/закрытого ключей с использованием RSA или ECDSA .

Структура JWT

Веб-токены JSON состоят из трех частей, разделенных точками .


  • Заголовок
 { "alg": "HS256", "typ": "JWT" }

Заголовок обычно состоит из двух частей: типа токена и используемого алгоритма подписи.


  • Полезная нагрузка
 { "sub": "1234567890", "name": "John Doe", "admin": true }

Полезная нагрузка содержит утверждения, которые представляют собой утверждения о пользователе. Затем полезная нагрузка кодируется Base64Url для формирования второй части веб-токена JSON. Описание стандартных полей, которые используются в качестве претензий, можно найти здесь .


  • Подпись
 HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

Чтобы создать часть подписи, вам необходимо взять закодированный заголовок, закодированные полезные данные, секрет и алгоритм, указанный в заголовке, и подписать их.


Токен обычно выглядит следующим образом:

xxxxx.yyyyy.zzzzz


Вы можете перейти на jwt.io и отладить образец токена или свой собственный. Просто вставьте свой токен в поле «Закодировано» и выберите « Алгоритм подписи токена».

отладчик jwt.io

.NET-проект

Теперь, когда у нас есть теоретические знания о том, как работает JWT, мы можем применить их к реальному проекту. Предположим, у нас есть простой API, который представляет операции CRUD для сущности кофе. Мы собираемся создать проект ASP.NET Core API, который представляет Coffee API. После этого мы создадим еще один проект ASP.NET Core API, который будет представлять собой Identity API, способный генерировать JWT. В реальной жизни вы, вероятно, будете использовать Identity Server , Okta или Auth0 для целей аутентификации/авторизации. Однако мы создадим собственный Identity API, чтобы продемонстрировать, как генерировать JWT. Когда Identity API будет готов, мы можем вызвать его контроллер и сгенерировать JWT на основе данных пользователя. Кроме того, мы можем защитить Coffee API с помощью конфигурации авторизации, которая требует передачи JWT при каждом запросе.

Ландшафт проекта .NET

API кофе

Сначала мы собираемся создать простой проект ASP.NET Core API, который представляет Coffee API. Вот структура этого проекта:

API кофе — Структура проекта


Начнем с Coffee.cs в папке Model . Это простая сущность со свойствами Id и Name .

 namespace Hackernoon.Coffee.API.Model; public class Coffee { public int Id { get; set; } public string Name { get; set; } }


Нам нужно хранить наши сущности во время работы с API. Итак, давайте представим простое хранилище в памяти. Он находится в файле Storage.cs в папке Data .

 namespace Hackernoon.Coffee.API.Data; public static class Storage { private static readonly List<Model.Coffee> Data = new(); public static List<Model.Coffee> GetAll() { return Data; } public static bool Create(Model.Coffee model) { if (Data.Any(c => c.Id == model.Id || c.Name == model.Name)) return false; Data.Add(new Model.Coffee { Id = model.Id, Name = model.Name }); return true; } public static bool Delete(int id) { if (Data.All(c => c.Id != id)) return false; Data.Remove(Storage.Data.First(c => c.Id == id)); return true; } public static bool Update(Model.Coffee model) { if (Data.All(c => c.Id != model.Id)) return false; Data.First(c => c.Id == model.Id).Name = model.Name; return true; } }


Нам нужен класс, который будет представлять запросы к Coffee API. Итак, давайте создадим CoffeeRequest.cs в папке Contracts .

 namespace Hackernoon.Coffee.API.Contracts; public class CoffeeRequest { public int Id { get; set; } public string Name { get; set; } }


Когда это будет сделано, мы можем реализовать CoffeeController.cs в папке Controller , которая представляет операции CRUD для сущности кофе.

 using Hackernoon.Coffee.API.Contracts; using Hackernoon.Coffee.API.Data; using Microsoft.AspNetCore.Mvc; namespace Hackernoon.Coffee.API.Controllers; [Route("coffee")] [ApiController] public class CoffeeController : ControllerBase { [HttpGet] public IList<Model.Coffee> GetAll() { return Storage.GetAll(); } [HttpPost] public IActionResult Create([FromBody]CoffeeRequest request) { var model = new Model.Coffee { Id = request.Id, Name = request.Name }; if (!Storage.Create(model)) return new BadRequestResult(); return new OkResult(); } [HttpDelete] public IActionResult Delete(int id) { if (!Storage.Delete(id)) return new BadRequestResult(); return new OkResult(); } [HttpPut] public IActionResult Update([FromBody] CoffeeRequest request) { var model = new Model.Coffee() { Id = request.Id, Name = request.Name }; if (!Storage.Update(model)) return new BadRequestResult(); return new OkResult(); } }


Coffee API готов, и мы можем запустить проект и увидеть пользовательский интерфейс Swagger следующим образом:

API кофе — пользовательский интерфейс Swagger

API идентификации

Давайте создадим еще один проект API ASP.NET Core, который представляет API идентификации. Вот структура этого проекта:

API идентификации — структура проекта

Начнем с файла TokenGenerationRequest.cs в папке Contracts , который представляет собой запрос на создание нового JWT со свойствами Email и Password .

 namespace Hackernoon.Identity.API.Contracts; public class TokenGenerationRequest { public string Email { get; set; } public string Password { get; set; } }


Нам нужно реализовать только TokenController.cs , который представляет собой логику генерации JWT. Но прежде чем мы это сделаем, необходимо установить пакет NuGet Microsoft.AspNetCore.Authentication.JwtBearer .

 using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Hackernoon.Identity.API.Contracts; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; namespace Hackernoon.Identity.API.Controllers; [Route("token")] public class TokenController : ControllerBase { private const string SecretKey = "VerySecretAndLongKey-NeedMoreSymbolsHere-123"; private const string Issuer = "IdentityServerIssuer"; private const string Audience = "IdentityServerClient"; private static readonly TimeSpan Lifetime = TimeSpan.FromMinutes(20); [HttpPost] public string Create([FromBody]TokenGenerationRequest request) { var claims = new List<Claim> {new Claim(ClaimTypes.Email, request.Email) }; var jwt = new JwtSecurityToken( issuer: Issuer, audience: Audience, claims: claims, expires: DateTime.UtcNow.Add(Lifetime), signingCredentials: CreateSigningCredentials()); return new JwtSecurityTokenHandler().WriteToken(jwt); } private static SigningCredentials CreateSigningCredentials() { return new SigningCredentials( new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey)), SecurityAlgorithms.HmacSha256); } }


Обратите внимание, что конфиденциальные константы, такие как SecretKey , Issuer и Audience , должны быть помещены где-то в конфигурации. Они жестко запрограммированы только для упрощения этого тестового проекта. В поле Lifetime установлено значение 20 минут, что означает, что токен будет действителен в течение этого времени. Вы также можете настроить этот параметр.


Теперь мы можем запустить проект и увидеть пользовательский интерфейс Swagger следующим образом:

API идентификации — пользовательский интерфейс Swagger


Давайте выполним вызов конечной точки /token и сгенерируем новый JWT. Попробуйте следующую полезную нагрузку:

 { "email": "[email protected]", "password": "password" }


Identity API сгенерирует соответствующий JWT:

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9lbWFpbGFkZHJlc3MiOiJqb2huLmRvZUBnbWFpbC5jb20iLCJJc0dvdXJtZXQiOiJmYWxzZSIsImV4cCI6MTcwNzc4Mzk4MCwiaXNzIjoiSWRlbnRpdHlTZXJ2ZXJJc3N1ZXIiLCJhdWQiOiJJZGVudGl0eVNlcnZlckNsaWVudCJ9.4odXsbWak1C0uK3Ux-n7f58icYQQwlHjM54OjgMCVPM

Включение авторизации в Coffee API

Теперь, когда Identity API готов и предоставляет нам токены, мы можем защитить Coffee API авторизацией. Опять же необходимо установить пакет NuGet Microsoft.AspNetCore.Authentication.JwtBearer .


Нам необходимо зарегистрировать необходимые сервисы службами аутентификации. Добавьте следующий код в файл Program.cs сразу после создания построителя.

 var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = "IdentityServerIssuer", ValidateAudience = true, ValidAudience = "IdentityServerClient", ValidateLifetime = true, IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes("VerySecretAndLongKey-NeedMoreSymbolsHere-123")), ValidateIssuerSigningKey = true, }; }); builder.Services.AddAuthorization();


Важно помнить, что порядок в промежуточном программном обеспечении важен. Мы включаем аутентификацию, вызывая метод AddAuthentication() и указывая JwtBearerDefaults.AuthenticationScheme в качестве схемы аутентификации. Это константа, содержащая значение Bearer .

 namespace Microsoft.AspNetCore.Authentication.JwtBearer { /// <summary>Default values used by bearer authentication.</summary> public static class JwtBearerDefaults { /// <summary> /// Default value for AuthenticationScheme property in the JwtBearerAuthenticationOptions /// </summary> public const string AuthenticationScheme = "Bearer"; } }


Нам нужно указать TokenValidationParameters , который описывает, какие параметры JWT будут проверяться во время авторизации. Мы также указываем IssuerSigningKey аналогичный signingCredentials в Identity API, для проверки подписи JWT. Более подробную информацию о TokenValidationParameters можно найти здесь .


Следующий фрагмент кода добавляет к сборщику промежуточное программное обеспечение, обеспечивающее возможности аутентификации и авторизации. Его следует добавить между методами UseHttpsRedirection() и MapControllers() .

 app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers();


Теперь мы можем использовать атрибут Authorize для контроллера или его действий. Применяя этот код, теперь все действия в CoffeeController защищены механизмом авторизации, и JWT необходимо отправлять как часть запроса.

 [Route("coffee")] [ApiController] [Authorize] public class CoffeeController : ControllerBase { ..


Если мы выполним вызов любой конечной точки Coffee API, мы сможем отладить HttpContext.User и увидеть, что он заполнен и имеет Identity с утверждениями, которые мы указали в JWT. Это важно для понимания того, как ASP.NET Core «под капотом» обрабатывает авторизацию.

Coffee API – заявки заполняются из JWT.

Добавьте авторизацию в пользовательский интерфейс Swagger

Мы проделали большую работу по защите Coffee API с авторизацией. Но если вы запустите проект Coffee API и откроете пользовательский интерфейс Swagger, вы не сможете отправить JWT как часть запроса. Чтобы это исправить, нам нужно обновить файл Program.cs , добавив следующий код:

 builder.Services.AddSwaggerGen(option => { option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { In = ParameterLocation.Header, Description = "Please enter a valid token", Name = "Authorization", Type = SecuritySchemeType.Http, BearerFormat = "JWT", Scheme = "Bearer" }); option.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type=ReferenceType.SecurityScheme, Id="Bearer" } }, new string[]{} } }); });


После этого мы сможем увидеть кнопку Авторизовать в правом верхнем углу:

Coffee API - появилась кнопка авторизации


Когда вы нажмете кнопку «Авторизовать» , вы сможете войти в JWT следующим образом:

API кофе — введите значение JWT

Используйте Postman для тестирования

Вы не можете ограничиваться использованием пользовательского интерфейса Swagger и можете выполнить тестирование API с помощью инструмента Postman. Давайте сначала вызовем конечную точку /token API идентификации. Нам нужно указать заголовок Content-Type со значением application/json в разделе «Заголовки» , поскольку мы собираемся использовать JSON в качестве полезной нагрузки.

API идентификации – указание заголовков


После этого мы можем вызвать конечную точку /token и получить новый JWT.

API идентификации — создание JWT


Теперь мы можем скопировать JWT и использовать его для вызова Coffee API. Нам нужно указать заголовок Content-Type аналогичный Identity API, если мы хотим тестировать, создавать и обновлять конечные точки. Заголовок Authorization также должен быть установлен со значением Bearer [your JWT value] . После этого просто нажмите кнопку «Отправить» и посмотрите результат.

Coffee API — получить все объекты

Ролевая авторизация

Как вы помните, полезная часть JWT представляет собой набор утверждений со значениями, которые точно представляют собой пары ключ-значение. Ролевая авторизация позволяет разграничить доступ к ресурсам приложения в зависимости от роли, к которой принадлежит пользователь.


Если мы обновим метод Create() в файле TokenController.cs в Identity API кодом, который добавляет новое утверждение для роли; мы можем обрабатывать аутентификацию на основе ролей в Coffee API. ClaimTypes.Role — это предопределенное имя утверждения роли.

 var claims = new List<Claim> { new Claim(ClaimTypes.Email, request.Email), new Claim(ClaimTypes.Role, "Barista") };


Обновите атрибут Authorize в файле CoffeeController.cs , указав имя роли:

 [Authorize(Roles = "Barista")]


Теперь все пользователи, которые вызывают Coffee API, должны иметь утверждение роли со значением Barista . В противном случае они получат код статуса 403 Forbidden .

Авторизация на основе претензий

Атрибут Authorize может легко обрабатывать аутентификацию на основе ролей. Но что, если этого недостаточно, и мы хотим разграничить доступ по каким-то свойствам пользователя, например возрасту или каким-либо другим? Вы, наверное, уже догадались, что в JWT можно добавлять свои утверждения и использовать их для построения логики авторизации. Авторизация на основе ролей сама по себе является частным случаем авторизации на основе утверждений, так же как роль — это тот же объект утверждения предопределенного типа.


Давайте обновим метод Create() в файле TokenController.cs в Identity API кодом, который добавляет новое утверждение IsGourmet .

 var claims = new List<Claim> { new Claim(ClaimTypes.Email, request.Email), new Claim("IsGourmet", "true") };


В файле Program.cs в Coffee API нам нужно создать политику, которая проверяет утверждение и может использоваться в атрибуте Authorize . Следующий код необходимо добавить сразу после вызова метода AddAuthentication() .

 builder.Services.AddAuthorization(opts => { opts.AddPolicy("OnlyForGourmet", policy => { policy.RequireClaim("IsGourmet", "true"); }); });


Обновите атрибут Authorize в файле CoffeeController.cs , указав имя политики:

 [Authorize(Policy = "OnlyForGourmet")]

Краткое содержание

Поздравляем! Вы приложили немало усилий для изучения JWT в .NET. Теперь вам необходимо иметь четкое представление о принципах JWT и о том, почему важно использовать его для авторизации в приложениях .NET. Но мы лишь прикоснулись к области аутентификации и авторизации в приложениях ASP.NET Core.


Я предлагаю изучить документацию Microsoft по темам, которые мы обсуждали в этой статье. В платформе .NET также имеется множество встроенных возможностей авторизации и управления ролями. Хорошим дополнением к этой статье может стать документация Microsoft по авторизации.