Gymterview
middle

Что такое JWT, какова его структура и лучшие практики использования?

JWT (JSON Web Token) — компактный самодостаточный токен для безопасной передачи информации между сторонами в формате JSON, широко используемый для аутентификации и авторизации в REST API.

Структура JWT

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

Header (заголовок) — алгоритм подписи и тип токена:

Пример
{
  "alg": "RS256",
  "typ": "JWT"
}

Payload (полезная нагрузка) — утверждения (claims):

Пример
{
  "sub": "user123",
  "name": "Иван Петров",
  "role": "MANAGER",
  "iat": 1700000000,
  "exp": 1700003600,
  "iss": "auth.mybank.com"
}

Стандартные claims: sub (субъект), iss (издатель), exp (срок действия), iat (время выдачи), aud (аудитория), jti (уникальный идентификатор).

Signature (подпись):

Пример
RSASHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  privateKey
)

Алгоритмы подписи

Алгоритм Тип Описание
HS256 Симметричный HMAC + SHA-256, один ключ для подписи и проверки
RS256 Асимметричный RSA + SHA-256, приватный ключ для подписи, публичный для проверки
ES256 Асимметричный ECDSA + SHA-256, компактнее RSA при той же надёжности

Для банковских систем рекомендуется RS256 или ES256 — публичный ключ можно безопасно распространять между сервисами.

Реализация в Spring Boot

Генерация и валидация JWT
// Генерация JWT
public String generateToken(UserDetails user) {
    return Jwts.builder()
        .setSubject(user.getUsername())
        .claim("role", user.getAuthorities())
        .setIssuedAt(new Date())
        .setExpiration(new Date(System.currentTimeMillis() + 3600_000))
        .setIssuer("auth.mybank.com")
        .setId(UUID.randomUUID().toString())
        .signWith(privateKey, SignatureAlgorithm.RS256)
        .compact();
}

// Валидация JWT
public Claims validateToken(String token) {
    return Jwts.parserBuilder()
        .setSigningKey(publicKey)
        .requireIssuer("auth.mybank.com")
        .build()
        .parseClaimsJws(token)
        .getBody();
}

Хранение токенов на клиенте

Хранилище Плюсы Минусы
HttpOnly Cookie Защита от XSS Уязвим к CSRF (нужна CSRF-защита)
localStorage Простота использования Уязвим к XSS
sessionStorage Очищается при закрытии вкладки Уязвим к XSS
В памяти (JS-переменная) Наибольшая безопасность Теряется при обновлении страницы

Рекомендуется: access token в памяти + refresh token в HttpOnly Secure Cookie.

Best Practices

  1. Короткое время жизни access token — 5-15 минут
  2. Refresh token — для обновления access token (хранить в HttpOnly cookie)
  3. Не хранить чувствительные данные в payload — JWT можно декодировать без ключа (Base64)
  4. Использовать jti (JWT ID) — для возможности отзыва токенов
  5. Ротировать ключи — периодическая смена ключей подписи
  6. Проверять все claims — exp, iss, aud, nbf
  7. Blacklist для отзыва — при logout добавлять jti в Redis blacklist
Пример
// Проверка отозванных токенов
public boolean isTokenRevoked(String jti) {
    return redisTemplate.hasKey("revoked:" + jti);
}

public void revokeToken(String jti, long expSeconds) {
    redisTemplate.opsForValue().set("revoked:" + jti, "true",
        expSeconds, TimeUnit.SECONDS);
}

На собеседовании: интервьюер хочет услышать структуру JWT (три части), разницу между HS256 и RS256, и понимание того, что payload не зашифрован, а лишь закодирован. Частая ошибка — не упомянуть стратегию отзыва токенов и не знать про jti.