Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
JSON Web Token (JWT): Необходимость расшифровки и безопасность
Отличный вопрос о JWT и криптографии! Ответ зависит от конкретного случая, но позвольме дать полное объяснение.
Краткий ответ: Зависит от типа и цели
JWT состоит из трёх частей:
JWT = Header.Payload.Signature
┌─────────┬─────────┬─────────┐
│ Base64 │ Base64 │ Signature
│ Encoded │ Encoded │ или Hash
└─────────┴─────────┴─────────┘
Структура JWT
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
public class JWTExample {
// Типичный JWT выглядит так:
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
// eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
// SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
public static void main(String[] args) {
// Декодирование JWT
String token = "eyJhbGc...";
DecodedJWT decodedJWT = JWT.decode(token);
// Заголовок
System.out.println("Algorithm: " + decodedJWT.getAlgorithm()); // HS256
System.out.println("Type: " + decodedJWT.getType()); // JWT
// Payload (Claims)
System.out.println("Subject: " + decodedJWT.getSubject()); // 1234567890
System.out.println("Name: " + decodedJWT.getClaim("name")); // John Doe
System.out.println("Issued At: " + decodedJWT.getIssuedAt()); // 1516239022
// Signature
System.out.println("Signature: " + decodedJWT.getSignature());
}
}
Шифрование vs Подпись (Signing)
ВАЖНО: JWT обычно НЕ зашифрован, он ПОДПИСАН!
ШИФРОВАНИЕ:
├─ Данные скрыты
├─ Только тот, кто имеет ключ, может прочитать
├─ Используется: SSL/TLS, HTTPS
└─ Примеры: AES, RSA encryption
ПОДПИСЬ (Signing):
├─ Данные ВИДНЫ, но защищены от изменения
├─ Сервер может проверить целостность
├─ Используется: HMAC, RSA signature
└─ Примеры: HS256, RS256
Может ли JWT быть расшифрован?
Коротко: Всегда можно декодировать Payload!
// ✅ МОЖНО ДЕКОДИРОВАТЬ PAYLOAD
// Base64 — это не шифрование, это кодирование
String payload = "eyJzdWIiOiIxMjM0NTY3ODkwIn0=";
String decoded = new String(Base64.getDecoder().decode(payload));
System.out.println(decoded); // {"sub":"1234567890"}
// ❌ НЕЛЬЗЯ ИЗМЕНИТЬ БЕЗ КЛЮЧА
// Если изменить payload, сигнатура перестанет совпадать
Когда нужно расшифровать JWT?
Случай 1: Проверка подписи (Validation)
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
public class JWTValidator {
private final String SECRET_KEY = "your-secret-key";
private final Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
public DecodedJWT validateToken(String token) {
try {
// ✅ Декодируем И проверяем подпись одновременно
DecodedJWT decodedJWT = JWT.require(algorithm)
.withIssuer("your-app") // Проверка издателя
.withClaim("sub", "1234567890") // Проверка claim
.acceptLeeway(5) // Допуск на временное несовпадение (5 сек)
.build()
.verify(token);
return decodedJWT;
} catch (JWTVerificationException exception) {
System.out.println("Invalid token: " + exception.getMessage());
return null;
}
}
}
Случай 2: Извлечение информации (Claims)
public class JWTExtraction {
public void extractClaims(String token) {
// Декодируем без проверки подписи (ОСТОРОЖНО!)
DecodedJWT jwt = JWT.decode(token);
String userId = jwt.getClaim("sub").asString();
String userName = jwt.getClaim("name").asString();
Integer roleId = jwt.getClaim("role_id").asInt();
List<String> permissions = jwt.getClaim("permissions").asList(String.class);
System.out.println("User ID: " + userId);
System.out.println("Name: " + userName);
System.out.println("Role: " + roleId);
System.out.println("Permissions: " + permissions);
}
}
Проблема: НЕ ЗАБЫВАЙТЕ ПРОВЕРЯТЬ ПОДПИСЬ!
// ❌ ОПАСНО: Декодировать без проверки подписи
public void unsafeDecoding(String token) {
DecodedJWT jwt = JWT.decode(token); // Только декодирование!
String userId = jwt.getClaim("sub").asString();
// ⚠️ Кто-то мог подделать этот токен!
// Подпись НЕ проверяется!
}
// ✅ ПРАВИЛЬНО: Проверять подпись ПЕРВЫМ
public void safeDecoding(String token) {
// Шаг 1: Проверяем подпись
DecodedJWT jwt = verifyToken(token);
if (jwt == null) {
throw new UnauthorizedException("Invalid token");
}
// Шаг 2: Только ТОГДА извлекаем данные
String userId = jwt.getClaim("sub").asString();
}
Spring Security + JWT
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
@Component
public class JWTAuthenticationFilter extends OncePerRequestFilter {
private final JWTProvider jwtProvider;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// Шаг 1: Извлечь токен из Header
String token = extractToken(request);
if (token != null) {
// Шаг 2: Проверить подпись И декодировать
DecodedJWT decodedJWT = jwtProvider.validateToken(token);
if (decodedJWT != null) {
// Шаг 3: Создать Authentication объект
String userId = decodedJWT.getClaim("sub").asString();
List<String> roles = decodedJWT.getClaim("roles").asList(String.class);
List<SimpleGrantedAuthority> authorities =
roles.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(
userId, null, authorities
);
// Шаг 4: Установить в SecurityContext
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
filterChain.doFilter(request, response);
}
private String extractToken(HttpServletRequest request) {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
return header.substring(7);
}
return null;
}
}
@Component
public class JWTProvider {
private final String SECRET_KEY = "your-secret-key";
private final Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
public DecodedJWT validateToken(String token) {
try {
return JWT.require(algorithm)
.build()
.verify(token);
} catch (JWTVerificationException e) {
return null;
}
}
}
RSA (асимметричное) шифрование для JWT
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
public class RSAJWTExample {
public static void main(String[] args) throws Exception {
// Генерируем пару ключей
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(2048);
KeyPair keyPair = keyPairGen.generateKeyPair();
// ПРИВАТНЫЙ КЛЮЧ (для подписи)
Algorithm algorithmSign = Algorithm.RSA256(
null, // публичный ключ не нужен для подписи
(RSAPrivateKey) keyPair.getPrivate()
);
// Создание токена
String token = JWT.create()
.withSubject("user-123")
.withIssuer("your-app")
.sign(algorithmSign);
// ПУБЛИЧНЫЙ КЛЮЧ (для проверки)
Algorithm algorithmVerify = Algorithm.RSA256(
(RSAPublicKey) keyPair.getPublic(),
null // приватный ключ не нужен для проверки
);
// Проверка токена
DecodedJWT jwt = JWT.require(algorithmVerify)
.build()
.verify(token);
System.out.println("Token verified: " + jwt.getSubject());
}
}
Сравнение HS256 vs RS256
| Параметр | HMAC (HS256) | RSA (RS256) |
|---|---|---|
| Тип | Симметричный (один ключ) | Асимметричный (два ключа) |
| Секрет | Одинаковый для подписи и проверки | Разные ключи |
| Масштабируемость | Сложнее в микросервисах | Легче в микросервисах |
| Производительность | Быстрее | Медленнее |
| Безопасность | Меньше если секрет скомпрометирован | Лучше (публичный ключ видно) |
| Распределение | Все должны знать секрет | Только публичный ключ |
Best Practices
// ✅ ПРАВИЛЬНО: Всегда проверяй подпись
public void authenticateUser(String token) {
try {
DecodedJWT jwt = validateAndDecodeToken(token);
processAuthentication(jwt);
} catch (JWTVerificationException e) {
throw new UnauthorizedException("Invalid token");
}
}
// ✅ ПРАВИЛЬНО: Устанавливай время истечения
String token = JWT.create()
.withSubject(userId)
.withExpiresAt(new Date(System.currentTimeMillis() + 3600000)) // 1 час
.sign(algorithm);
// ✅ ПРАВИЛЬНО: Используй Refresh Token для продления
@PostMapping("/refresh")
public TokenResponse refreshToken(@RequestBody RefreshTokenRequest request) {
String refreshToken = request.getRefreshToken();
// Проверяем refresh token
DecodedJWT decodedRefresh = validateRefreshToken(refreshToken);
// Создаём новый access token
String newAccessToken = createAccessToken(
decodedRefresh.getClaim("sub").asString()
);
return new TokenResponse(newAccessToken);
}
// ✅ ПРАВИЛЬНО: Не кэшируй секретные данные в payload
// НЕПРАВИЛЬНО:
public String createBadToken(User user) {
return JWT.create()
.withClaim("password", user.getPassword()) // ❌ НИКОГДА!
.withClaim("ssn", user.getSsn()) // ❌ НИКОГДА!
.sign(algorithm);
}
// ПРАВИЛЬНО:
public String createGoodToken(User user) {
return JWT.create()
.withSubject(user.getId().toString())
.withClaim("email", user.getEmail())
.withClaim("roles", user.getRoles())
.sign(algorithm);
}
Выводы
- JWT НЕ шифруется, он кодируется (Base64) — Payload видно всем
- Payload ВСЕГДА может быть декодирован — это нормально
- Защита — в подписи — только сервер может подделать
- ОБЯЗАТЕЛЬНО проверяй подпись перед использованием данных
- Используй HMAC или RSA для подписи
- Никогда не кэшируй чувствительные данные в JWT
- Устанавливай TTL (время истечения) для токенов
- Используй HTTPS для передачи JWT (запретить просмотр в пути)