Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как происходит защита JWT токена от подделки
JWT (JSON Web Token) — это широко используемый стандарт аутентификации, который обеспечивает защиту от подделки благодаря **криптографической подписи**. Рассмотрим механизм защиты в деталях.
Структура JWT
JWT состоит из трёх частей, разделённых точками:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
- Header (заголовок) — тип токена и алгоритм хеширования
- Payload (нагрузка) — данные пользователя (claims)
- Signature (подпись) — криптографическая подпись
Как создаётся подпись
Серверная сторона создаёт подпись по следующему алгоритму:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Date;
public class JwtProvider {
private final SecretKey secretKey;
public JwtProvider(String secret) {
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes());
}
public String generateToken(String userId) {
return Jwts.builder()
.setSubject(userId)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 3600000))
.signWith(secretKey, SignatureAlgorithm.HS256)
.compact();
}
}
Физически подпись вычисляется так:
SIGNATURE = HMAC-SHA256(
secretKey,
base64url(header) + "." + base64url(payload)
)
Целая точка: если кто-то попытается изменить header или payload, подпись станет неверной.
Верификация подписи на сервере
Когда клиент отправляет токен, сервер проверяет его подлинность:
public class JwtValidator {
private final SecretKey secretKey;
public JwtValidator(String secret) {
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes());
}
public String validateToken(String token) {
try {
return Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
} catch (JwtException | IllegalArgumentException e) {
throw new SecurityException("Invalid JWT token", e);
}
}
}
Процесс верификации:
- Парсинг — разделяем токен на header, payload и signature
- Воссоздание подписи — берём header + payload и пересчитываем HMAC с серверным secret key
- Сравнение — если пересчитанная подпись совпадает с полученной, токен подлинный
// Псевдокод верификации
String[] parts = token.split("\\.");
String header = parts[0];
String payload = parts[1];
String receivedSignature = parts[2];
// Пересчитываем подпись
String expectedSignature = HMAC_SHA256(
secretKey,
header + "." + payload
);
// Проверяем совпадение
if (!constantTimeEquals(receivedSignature, expectedSignature)) {
throw new SecurityException("Invalid signature");
}
Почему подделка невозможна
-
Нет секретного ключа — Злоумышленник может видеть header и payload (они только base64-encoded, не зашифрованы), но не может пересчитать подпись без secret key.
-
HMAC необратимо — Функция HMAC-SHA256 — это односторонняя хеш-функция. Зная подпись, невозможно восстановить secret key.
-
Защита от временных атак — Java JWT библиотеки (как JJWT) используют
constantTimeEquals()для сравнения подписей, чтобы предотвратить timing attacks.
Алгоритмы подписей
| Алгоритм | Тип | Безопасность | Использование |
|---|---|---|---|
| HS256 | HMAC + SHA-256 | Симметричный, быстро | Внутренние сервисы |
| HS512 | HMAC + SHA-512 | Симметричный,強е | Критичные данные |
| RS256 | RSA + SHA-256 | Асимметричный | Микросервисы, публичные API |
| ES256 | ECDSA | Асимметричный, компактный | Mobile, высокий throughput |
Пример с асимметричным ключом (RS256)
Для микросервисной архитектуры безопаснее использовать RSA:
import java.security.KeyPair;
import java.security.KeyPairGenerator;
public class JwtProviderRSA {
private final PrivateKey privateKey;
private final PublicKey publicKey;
public JwtProviderRSA() throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair keyPair = keyGen.generateKeyPair();
this.privateKey = keyPair.getPrivate();
this.publicKey = keyPair.getPublic();
}
public String generateToken(String userId) {
return Jwts.builder()
.setSubject(userId)
.signWith(privateKey, SignatureAlgorithm.RS256)
.compact();
}
public String validateToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(publicKey)
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
}
}
Преимущество RS256: публичный ключ может быть распределён (например, другие сервисы могут валидировать токены), а приватный ключ остаётся защищённым.
Практические рекомендации
- Никогда не меняйте secret key — это сделает все существующие токены невалидными.
- Хранните secret key в environment переменных, не в коде.
- Используйте HTTPS — передавайте JWT только по защищённому каналу.
- Установите expiration — добавляйте
setExpiration()для автоматического истечения токенов. - Используйте RS256 для микросервисов — где разные сервисы должны валидировать токены без доступа к secret key.
- Проверяйте токен на каждом запросе — в фильтрах или интерсепторах.
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtValidator jwtValidator;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = extractToken(request);
if (token != null) {
try {
String userId = jwtValidator.validateToken(token);
// Установить authentication в SecurityContext
} catch (SecurityException e) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
}
filterChain.doFilter(request, response);
}
}
Таким образом, JWT обеспечивает криптографическую гарантию подлинности благодаря HMAC или RSA подписи. Без знания secret key (или приватного ключа) невозможно создать валидный подделанный токен.