Как работает технология аутентификации на базе JWT (JSON Web Token)?

«Как работает технология аутентификации на базе JWT (JSON Web Token)?» — вопрос из категории Безопасность, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

JWT — это открытый стандарт (RFC 7519) для создания токенов доступа, которые позволяют безопасно передавать информацию между сторонами в виде компактного JSON-объекта. Токен подписан цифровой подписью, что позволяет проверять его целостность и подлинность.

Структура JWT: Токен состоит из трех частей, разделенных точками: header.payload.signature

  1. Header (Заголовок): Содержит тип токена (JWT) и алгоритм подписи (например, HS256 или RS256).
    {
      "alg": "HS256",
      "typ": "JWT"
    }
  2. Payload (Полезная нагрузка): Содержит утверждения (claims) — утверждения о пользователе и дополнительных данных. Стандартные claims: sub (subject — идентификатор пользователя), exp (expiration time — срок действия), iat (issued at — время выдачи).
    {
      "sub": "1234567890",
      "name": "John Doe",
      "role": "admin",
      "exp": 1516239022
    }
  3. Signature (Подпись): Создается путем кодирования header и payload в Base64Url, их объединения с точкой и последующей подписи с использованием секретного ключа (для HMAC) или приватного ключа (для RSA) по алгоритму, указанному в header. HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

Типичный поток аутентификации с JWT:

  1. Логин: Пользователь отправляет учетные данные (логин/пароль) на сервер аутентификации.
  2. Верификация и генерация токена: Сервер проверяет учетные данные и, если они верны, генерирует JWT, подписывает его секретным ключом и отправляет клиенту (обычно в теле ответа или в cookie).
  3. Хранение и отправка токена: Клиент (браузер, мобильное приложение) сохраняет токен (часто в localStorage или HttpOnly cookie) и включает его в заголовок Authorization последующих запросов к защищенным API.
    Authorization: Bearer <your-jwt-token>
  4. Верификация токена на защищенных ресурсах: Сервер (или API-шлюз) получает токен, проверяет его подпись на валидность с помощью секретного/публичного ключа, убеждается, что срок действия (exp) не истек, и извлекает claims (например, role) для авторизации доступа к ресурсу.

Ключевые преимущества:

  • Бессостояние (Stateless): Серверу не нужно хранить сессию пользователя. Вся необходимая информация содержится в самом токене.
  • Масштабируемость: Легко масштабировать, так как любой сервер с секретным/публичным ключом может проверить токен.
  • Гибкость: В payload можно включать любые пользовательские данные (claims).

Критические моменты безопасности:

  • Секретный ключ: Должен храниться в безопасности на сервере. Компрометация ключа позволяет злоумышленнику генерировать любые токены.
  • Небезопасное хранение на клиенте: Хранение в localStorage уязвимо к XSS-атакам. Более безопасный вариант — HttpOnly, Secure, SameSite cookies.
  • Невозможность отзыва: JWT действует до истечения срока (exp). Для реализации мгновенного выхода (logout) требуется дополнительная архитектура (черные списки токенов, короткий срок жизни + refresh tokens).

Пример генерации и проверки токена на C# (библиотека System.IdentityModel.Tokens.Jwt):

// Генерация токена
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your_super_secret_key"));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

var claims = new[]
{
    new Claim(JwtRegisteredClaimNames.Sub, "user123"),
    new Claim("role", "admin")
};

var token = new JwtSecurityToken(
    issuer: "your_issuer",
    audience: "your_audience",
    claims: claims,
    expires: DateTime.Now.AddMinutes(30),
    signingCredentials: credentials
);

var tokenString = new JwtSecurityTokenHandler().WriteToken(token);

// Проверка токена
var tokenHandler = new JwtSecurityTokenHandler();
tokenHandler.ValidateToken(tokenString, new TokenValidationParameters
{
    ValidateIssuerSigningKey = true,
    IssuerSigningKey = securityKey,
    ValidateIssuer = true,
    ValidIssuer = "your_issuer",
    ValidateAudience = true,
    ValidAudience = "your_audience",
    ValidateLifetime = true,
    ClockSkew = TimeSpan.Zero // Точная проверка времени
}, out SecurityToken validatedToken);