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 } Полезная нагрузка содержит утверждения, которые представляют собой утверждения о пользователе. Затем полезная нагрузка кодируется для формирования второй части веб-токена JSON. Описание стандартных полей, которые используются в качестве претензий, можно найти . Base64Url здесь Подпись HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) Чтобы создать часть подписи, вам необходимо взять закодированный заголовок, закодированные полезные данные, секрет и алгоритм, указанный в заголовке, и подписать их. Токен обычно выглядит следующим образом: xxxxx.yyyyy.zzzzz Вы можете перейти на и отладить образец токена или свой собственный. Просто вставьте свой токен в поле и выберите « подписи токена». jwt.io «Закодировано» Алгоритм .NET-проект Теперь, когда у нас есть теоретические знания о том, как работает JWT, мы можем применить их к реальному проекту. Предположим, у нас есть простой API, который представляет операции CRUD для сущности кофе. Мы собираемся создать проект ASP.NET Core API, который представляет Coffee API. После этого мы создадим еще один проект ASP.NET Core API, который будет представлять собой Identity API, способный генерировать JWT. В реальной жизни вы, вероятно, будете использовать , или для целей аутентификации/авторизации. Однако мы создадим собственный Identity API, чтобы продемонстрировать, как генерировать JWT. Когда Identity API будет готов, мы можем вызвать его контроллер и сгенерировать JWT на основе данных пользователя. Кроме того, мы можем защитить Coffee API с помощью конфигурации авторизации, которая требует передачи JWT при каждом запросе. Identity Server Okta Auth0 API кофе Сначала мы собираемся создать простой проект ASP.NET Core API, который представляет Coffee 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; } } Когда это будет сделано, мы можем реализовать в папке , которая представляет операции CRUD для сущности кофе. CoffeeController.cs Controller 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 идентификации Давайте создадим еще один проект API ASP.NET Core, который представляет API идентификации. Вот структура этого проекта: Начнем с файла в папке , который представляет собой запрос на создание нового JWT со свойствами и . TokenGenerationRequest.cs Contracts Email Password namespace Hackernoon.Identity.API.Contracts; public class TokenGenerationRequest { public string Email { get; set; } public string Password { get; set; } } Нам нужно реализовать только , который представляет собой логику генерации JWT. Но прежде чем мы это сделаем, необходимо установить пакет NuGet . TokenController.cs 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); } } Обратите внимание, что конфиденциальные константы, такие как , и , должны быть помещены где-то в конфигурации. Они жестко запрограммированы только для упрощения этого тестового проекта. В поле установлено значение 20 минут, что означает, что токен будет действителен в течение этого времени. Вы также можете настроить этот параметр. SecretKey Issuer Audience Lifetime Теперь мы можем запустить проект и увидеть пользовательский интерфейс Swagger следующим образом: Давайте выполним вызов конечной точки и сгенерируем новый JWT. Попробуйте следующую полезную нагрузку: /token { "email": "john.doe@gmail.com", "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"; } } Нам нужно указать , который описывает, какие параметры JWT будут проверяться во время авторизации. Мы также указываем аналогичный в Identity API, для проверки подписи JWT. Более подробную информацию о можно найти . TokenValidationParameters IssuerSigningKey signingCredentials TokenValidationParameters здесь Следующий фрагмент кода добавляет к сборщику промежуточное программное обеспечение, обеспечивающее возможности аутентификации и авторизации. Его следует добавить между методами и . UseHttpsRedirection() MapControllers() app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); Теперь мы можем использовать атрибут для контроллера или его действий. Применяя этот код, теперь все действия в защищены механизмом авторизации, и JWT необходимо отправлять как часть запроса. Authorize CoffeeController [Route("coffee")] [ApiController] [Authorize] public class CoffeeController : ControllerBase { .. Если мы выполним вызов любой конечной точки Coffee API, мы сможем отладить и увидеть, что он заполнен и имеет с утверждениями, которые мы указали в JWT. Это важно для понимания того, как ASP.NET Core «под капотом» обрабатывает авторизацию. HttpContext.User Identity Добавьте авторизацию в пользовательский интерфейс 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[]{} } }); }); После этого мы сможем увидеть кнопку в правом верхнем углу: Авторизовать Когда вы нажмете кнопку , вы сможете войти в JWT следующим образом: «Авторизовать» Используйте Postman для тестирования Вы не можете ограничиваться использованием пользовательского интерфейса Swagger и можете выполнить тестирование API с помощью инструмента Postman. Давайте сначала вызовем конечную точку API идентификации. Нам нужно указать заголовок со значением в разделе , поскольку мы собираемся использовать JSON в качестве полезной нагрузки. /token Content-Type application/json «Заголовки» После этого мы можем вызвать конечную точку и получить новый JWT. /token Теперь мы можем скопировать JWT и использовать его для вызова Coffee API. Нам нужно указать заголовок аналогичный Identity API, если мы хотим тестировать, создавать и обновлять конечные точки. Заголовок также должен быть установлен со значением . После этого просто нажмите кнопку и посмотрите результат. Content-Type Authorization Bearer [your JWT value] «Отправить» Ролевая авторизация Как вы помните, полезная часть JWT представляет собой набор утверждений со значениями, которые точно представляют собой пары ключ-значение. Ролевая авторизация позволяет разграничить доступ к ресурсам приложения в зависимости от роли, к которой принадлежит пользователь. Если мы обновим метод в файле в Identity API кодом, который добавляет новое утверждение для роли; мы можем обрабатывать аутентификацию на основе ролей в Coffee API. — это предопределенное имя утверждения роли. Create() TokenController.cs 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 Авторизация на основе претензий Атрибут может легко обрабатывать аутентификацию на основе ролей. Но что, если этого недостаточно, и мы хотим разграничить доступ по каким-то свойствам пользователя, например возрасту или каким-либо другим? Вы, наверное, уже догадались, что в JWT можно добавлять свои утверждения и использовать их для построения логики авторизации. Авторизация на основе ролей сама по себе является частным случаем авторизации на основе утверждений, так же как роль — это тот же объект утверждения предопределенного типа. Authorize Давайте обновим метод в файле в Identity API кодом, который добавляет новое утверждение . Create() TokenController.cs 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 по авторизации. документация