Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Валидация JSON Web Token (JWT)
JWT (JSON Web Token) — это компактный, самодостаточный способ для безопасной передачи информации между сторонами. Валидация JWT включает проверку подписи, истечения срока и других параметров.
Структура JWT
JWT состоит из трёх частей, разделённых точками:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
↓ ↓ ↓
Header Payload Signature
Компоненты JWT
// Header
{
"alg": "HS256", // Алгоритм
"typ": "JWT" // Тип токена
}
// Payload (Claims)
{
"sub": "1234567890", // Subject (кому)
"name": "John Doe", // Имя
"iat": 1516239022, // Issued At (когда выдан)
"exp": 1516242622, // Expiration (когда истекает)
"roles": ["USER", "ADMIN"]
}
// Signature
HMAC_SHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret_key
)
1. Валидация с использованием jjwt библиотеки
Моста популярная библиотека для работы с JWT:
<!-- pom.xml -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
2. Базовая валидация JWT
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
public class JwtValidator {
private final SecretKey key;
private final JwtParser parser;
public JwtValidator(String secret) {
// Ключ для подписи
this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
// Создаём парсер с ключом
this.parser = Jwts.parserBuilder()
.setSigningKey(key)
.build();
}
// Валидация и получение claims
public Claims validateToken(String token) {
try {
// Парсим и проверяем подпись
Jws<Claims> jws = parser.parseClaimsJws(token);
return jws.getBody();
} catch (JwtException e) {
throw new InvalidTokenException("Invalid JWT token", e);
}
}
// Проверка истечения
public boolean isTokenExpired(String token) {
try {
Claims claims = validateToken(token);
return claims.getExpiration().before(new Date());
} catch (JwtException e) {
return true;
}
}
}
3. Полная валидация со всеми проверками
public class TokenValidator {
private final SecretKey key;
private final JwtParser parser;
public TokenValidator(String secret) {
this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
this.parser = Jwts.parserBuilder()
.setSigningKey(key)
.requireIssuer("your-app")
.requireAudience("your-api")
.build();
}
public Claims validateAndGetClaims(String token) throws JwtValidationException {
try {
// 1. Проверка подписи — автоматически
Jws<Claims> jws = parser.parseClaimsJws(token);
Claims claims = jws.getBody();
// 2. Проверка истечения срока
if (isExpired(claims)) {
throw new JwtValidationException("Token expired");
}
// 3. Проверка необходимых claims
if (!hasClaim(claims, "sub")) {
throw new JwtValidationException("Missing subject claim");
}
return claims;
} catch (SignatureException e) {
throw new JwtValidationException("Invalid signature", e);
} catch (MalformedJwtException e) {
throw new JwtValidationException("Invalid token format", e);
} catch (ExpiredJwtException e) {
throw new JwtValidationException("Token expired", e);
} catch (UnsupportedJwtException e) {
throw new JwtValidationException("Unsupported token", e);
} catch (IllegalArgumentException e) {
throw new JwtValidationException("Invalid token", e);
}
}
private boolean isExpired(Claims claims) {
Date expiration = claims.getExpiration();
return expiration != null && expiration.before(new Date());
}
private boolean hasClaim(Claims claims, String claimName) {
return claims.get(claimName) != null;
}
}
4. Spring Security с JWT
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter(JwtValidator validator) {
return new JwtAuthenticationFilter(validator);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http, JwtAuthenticationFilter filter)
throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/login").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
5. JWT фильтр для Spring Security
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtValidator validator;
public JwtAuthenticationFilter(JwtValidator validator) {
this.validator = validator;
}
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
try {
String token = extractTokenFromRequest(request);
if (token != null) {
// Валидируем токен
Claims claims = validator.validateToken(token);
// Создаём Authentication объект
String username = claims.getSubject();
List<String> roles = claims.get("roles", List.class);
List<GrantedAuthority> authorities = roles.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(
username,
null,
authorities
);
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (JwtValidationException e) {
response.sendError(
HttpServletResponse.SC_UNAUTHORIZED,
"Invalid JWT token: " + e.getMessage()
);
return;
}
filterChain.doFilter(request, response);
}
private String extractTokenFromRequest(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7);
}
return null;
}
}
6. Создание и валидация токенов
@Service
public class AuthService {
private final SecretKey key;
private final JwtValidator validator;
public AuthService(String secret, JwtValidator validator) {
this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
this.validator = validator;
}
// Создание токена
public String generateToken(String username, List<String> roles) {
long nowMillis = System.currentTimeMillis();
long expirationMillis = nowMillis + 3600000; // 1 час
return Jwts.builder()
.setSubject(username)
.claim("roles", roles)
.claim("iat", nowMillis / 1000)
.setIssuedAt(new Date(nowMillis))
.setExpiration(new Date(expirationMillis))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
// Валидация и получение пользователя
public String getUsernameFromToken(String token) {
Claims claims = validator.validateToken(token);
return claims.getSubject();
}
// Получение ролей
public List<String> getRolesFromToken(String token) {
Claims claims = validator.validateToken(token);
return claims.get("roles", List.class);
}
}
7. Обработка исключений при валидации
public class JwtExceptionHandler {
public static void handleJwtException(Exception e) {
if (e instanceof SignatureException) {
// Подпись невалидна
System.out.println("Invalid signature");
} else if (e instanceof MalformedJwtException) {
// Токен в неправильном формате
System.out.println("Invalid token format");
} else if (e instanceof ExpiredJwtException) {
// Токен истёк
System.out.println("Token expired");
} else if (e instanceof UnsupportedJwtException) {
// Неподдерживаемый формат
System.out.println("Unsupported token");
} else if (e instanceof IllegalArgumentException) {
// Пустой или null
System.out.println("Empty token");
}
}
}
8. Проверка с использованием public ключа (асимметричное)
Для асимметричной подписи (RS256):
public class RsaJwtValidator {
private final PublicKey publicKey;
private final JwtParser parser;
public RsaJwtValidator(String publicKeyPem) throws Exception {
this.publicKey = loadPublicKey(publicKeyPem);
this.parser = Jwts.parserBuilder()
.setSigningKey(publicKey)
.build();
}
public Claims validateToken(String token) {
try {
return parser.parseClaimsJws(token).getBody();
} catch (JwtException e) {
throw new InvalidTokenException("Invalid JWT: " + e.getMessage());
}
}
private PublicKey loadPublicKey(String publicKeyPem) throws Exception {
String publicKeyContent = publicKeyPem
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\n", "")
.replaceAll("\\r", "");
byte[] decoded = Base64.getDecoder().decode(publicKeyContent);
X509EncodedKeySpec spec = new X509EncodedKeySpec(decoded);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePublic(spec);
}
}
9. Валидация с Refresh Token
public class TokenPair {
private String accessToken; // Короткоживущий (15 минут)
private String refreshToken; // Долгоживущий (7 дней)
}
@Service
public class RefreshTokenService {
public TokenPair refreshAccessToken(String refreshToken) {
try {
// Валидируем refresh токен
Claims claims = validator.validateToken(refreshToken);
String username = claims.getSubject();
List<String> roles = claims.get("roles", List.class);
// Генерируем новый access токен
String newAccessToken = generateAccessToken(username, roles);
return new TokenPair(newAccessToken, refreshToken);
} catch (JwtException e) {
throw new InvalidTokenException("Cannot refresh token", e);
}
}
}
Лучшие практики валидации JWT
- Всегда проверяй подпись — это основная защита
- Проверяй истечение токена — используй exp claim
- Валидируй необходимые claims — sub, iat, exp
- Используй secure ключи — достаточно длинные (256+ бит)
- Ротация ключей — периодически обновляй secret
- HTTPS только — JWT передаётся по защищённому каналу
- Не храни чувствительные данные — в payload это видимо
- Используй refresh tokens — access токены с коротким TTL
- Обработка исключений — правильно обрабатывай ошибки
- Логирование — логируй попытки с невалидными токенами
Валидация JWT — критический элемент безопасности. Правильная реализация защищает приложение от подделанных токенов и несанкционированного доступа.