Как вы обрабатывали и валидировали JWT-токен в заголовке запроса?

«Как вы обрабатывали и валидировали JWT-токен в заголовке запроса?» — вопрос из категории Безопасность, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Обработка входящего JWT (JSON Web Token) включала несколько этапов валидации и извлечения данных для аутентификации и авторизации.

Стандартный поток обработки:

  1. Извлечение токена: Токен приходил в заголовке Authorization в формате Bearer <token>. Первым делом проверялось наличие и корректность этого префикса.
  2. Парсинг и верификация: Использовалась библиотека JJWT. Ключевые проверки:
    • Структура: Проверка, что токен состоит из трех частей (header.payload.signature), разделенных точками.
    • Подпись (Signature): Верификация с использованием секретного ключа (HMAC) или публичного ключа (RSA), чтобы убедиться, что токен не был изменен.
    • Срок действия (Expiration): Проверка claim'а exp (expiration time).
    • Аудитория (Audience) и издатель (Issuer): При необходимости проверялись claims aud и iss.
  3. Извлечение данных (Claims): После успешной верификации из payload токена извлекалась информация о пользователе (например, sub — subject/userId) и его роли (roles).

Пример кода на Java с использованием JJWT:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;

public JwtUserInfo validateAndParseToken(String authHeader) {
    if (authHeader == null || !authHeader.startsWith("Bearer ")) {
        throw new SecurityException("Missing or invalid Authorization header");
    }
    String jwtToken = authHeader.substring(7); // Убираем "Bearer "

    Claims claims = Jwts.parserBuilder()
        .setSigningKey(secretKey) // Секретный ключ для верификации подписи
        .build()
        .parseClaimsJws(jwtToken)
        .getBody();

    // Извлечение данных из claims
    String userId = claims.getSubject();
    List<String> roles = (List<String>) claims.get("roles");

    return new JwtUserInfo(userId, roles);
}

Дополнительные меры:

  • Для реализации Refresh Token использовался отдельный эндпоинт. Refresh-токены хранились в Redis с TTL для возможности их отзыва.
  • В микросервисной архитектуре секретный/публичный ключ часто хранился в защищенном хранилище (например, HashiCorp Vault).