Что такое 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
- Короткое время жизни access token — 5-15 минут
- Refresh token — для обновления access token (хранить в HttpOnly cookie)
- Не хранить чувствительные данные в payload — JWT можно декодировать без ключа (Base64)
- Использовать
jti(JWT ID) — для возможности отзыва токенов - Ротировать ключи — периодическая смена ключей подписи
- Проверять все claims —
exp,iss,aud,nbf - 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.