Перейти к содержанию

4. JWT, роли и доступ

JWT-токен подтверждает, что пользователь прошёл вход. Роль внутри токена позволяет защищать эндпоинты.

Без авторизации API доверяет любому клиенту. Это опасно: любой человек мог бы удалить события, выгрузить отчёт или создать фальшивые данные. Поэтому сначала пользователь проходит вход, а затем доказывает свою личность с помощью токена.

JWT удобно использовать в Web API, потому что серверу не нужно хранить сессию в памяти. Клиент отправляет токен в каждом запросе, а сервер проверяет подпись и срок действия.

Типовой поток такой:

  1. Пользователь отправляет логин и пароль на /api/auth/login.
  2. Сервер проверяет данные в базе.
  3. Сервер создаёт JWT с идентификатором пользователя и ролью.
  4. Клиент сохраняет токен.
  5. В следующих запросах клиент отправляет Authorization: Bearer <token>.
  6. ASP.NET Core проверяет токен до входа в контроллер.

Пакет

JWT-проверка в ASP.NET Core подключается отдельным пакетом. Он добавляет middleware и настройки, которые позволяют серверу читать заголовок Authorization: Bearer ... и проверять подпись токена.

dotnet add ActivityMonitoring.Api package Microsoft.AspNetCore.Authentication.JwtBearer

Настройки

Параметры JWT выносятся в конфигурацию, чтобы не зашивать их прямо в код. Issuer и Audience помогают проверить, кто выпустил токен и для какого клиента он предназначен, а Key используется для подписи.

appsettings.json:

{
  "Jwt": {
    "Issuer": "ActivityMonitoring",
    "Audience": "ActivityMonitoringClient",
    "Key": "change-this-key-to-long-production-secret"
  }
}

Генерация токена

Токен содержит claims — утверждения о пользователе. В практике используются три claim: идентификатор, имя пользователя и роль. Пароль, секретные данные и персональные данные в JWT хранить нельзя: токен может быть прочитан клиентом.

public class JwtTokenFactory
{
    private readonly IConfiguration _configuration;

    public JwtTokenFactory(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public string Create(AppUser user)
    {
        var claims = new[]
        {
            new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
            new Claim(ClaimTypes.Name, user.UserName),
            new Claim(ClaimTypes.Role, user.Role.Name)
        };

        var key = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]!));

        var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(
            issuer: _configuration["Jwt:Issuer"],
            audience: _configuration["Jwt:Audience"],
            claims: claims,
            expires: DateTime.UtcNow.AddHours(4),
            signingCredentials: credentials);

        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}

Подключение JWT

После генерации токенов нужно научить API их проверять. Этот блок регистрирует схему Bearer-аутентификации и задаёт правила валидации для каждого входящего токена.

builder.Services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!))
        };
    });

builder.Services.AddAuthorization();

В конвейере эти middleware должны выполняться до маршрутов контроллеров. Сначала приложение устанавливает личность пользователя по токену, затем проверяет, имеет ли он право вызвать конкретный endpoint.

app.UseAuthentication();
app.UseAuthorization();

Защита эндпоинтов

Атрибут [Authorize] говорит: “сюда можно только авторизованным пользователям”. Если указать роли, ASP.NET Core дополнительно проверит claim ClaimTypes.Role.

[Authorize]
[HttpGet]
public async Task<ActionResult<IReadOnlyList<ActivityEventDto>>> Get(...)
{
    ...
}

[Authorize(Roles = "Admin")]
[HttpDelete("{id:int}")]
public async Task<IActionResult> Delete(int id, CancellationToken ct)
{
    ...
}

[Authorize(Roles = "Admin,Analyst")]
[HttpGet("/api/analytics/summary")]
public async Task<ActionResult<AnalyticsSummaryDto>> Summary(CancellationToken ct)
{
    ...
}

Роли практики

Роль Возможности
Admin все операции, удаление событий, отчёты, управление данными
Analyst просмотр событий, аналитика, отчёты
Operator создание и просмотр событий без удаления и отчётов

Частые ошибки авторизации

Ошибка Причина Что проверить
401 Unauthorized Токен отсутствует, истёк или неверно подписан Заголовок Authorization, Jwt:Key, срок действия
403 Forbidden Пользователь вошёл, но роль не подходит Claim роли и атрибут [Authorize(Roles = "...")]
Swagger не отправляет токен Не настроена схема Bearer в Swagger Добавить настройку безопасности Swagger
Все токены стали недействительными Изменился Jwt:Key Войти заново и получить новый токен