← Назад к вопросам
Можно ли проверить только подпись при валидации JSON Web Token?
2.0 Middle🔥 191 комментариев
#Другое
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Валидация подписи JSON Web Token
Да, можно проверить только подпись при валидации JWT, но это опасный и неправильный подход в production среде. Опытный разработчик должен понимать почему, и всегда проверять все компоненты токена.
Структура JSON Web Token
JWT состоит из трёх частей, разделённых точками:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
- Header (заголовок) — информация об алгоритме
- Payload (полезная нагрузка) — данные пользователя
- Signature (подпись) — криптографическая подпись
Технически можно, но практически нельзя
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import java.nio.charset.StandardCharsets;
public class JwtValidator {
// ПЛОХОЙ ПРИМЕР: проверка ТОЛЬКО подписи
public boolean validateJwtSignatureOnly(String token, String secret) {
try {
Jwts.parserBuilder()
.setSigningKey(
Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8))
)
.build()
.parseClaimsJws(token);
return true; // Подпись верна
} catch (Exception e) {
return false; // Подпись неверна
}
}
}
Технически это работает, но не проверяет критические параметры.
Почему проверка только подписи опасна
1. Не проверяется истечение срока (exp)
public class SecurityFlaw {
// Токен давно истёк, но подпись ещё действительна!
public void vulnerableEndpoint(String token, String secret) {
if (validateJwtSignatureOnly(token, secret)) {
// УЯЗВИМОСТЬ: старый токен всё ещё принимается
// Пользователь мог быть заблокирован
processRequest();
}
}
}
2. Не проверяется аудитория (aud)
public class AudienceVulnerability {
// Если не проверить aud, токен можно использовать в других сервисах
public void loginService(String token, String secret) {
if (validateJwtSignatureOnly(token, secret)) {
// Токен, выданный для API, используется в web-приложении
processLogin();
}
}
}
3. Не проверяется издатель (iss)
public class IssuerVulnerability {
// Токен от другого провайдера может быть принят
public boolean isValidToken(String token, String secret) {
// Проверка подписи может пройти, если у них одинаковый secret
return validateJwtSignatureOnly(token, secret);
}
}
Правильная валидация JWT
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import java.nio.charset.StandardCharsets;
import java.util.Date;
public class SecureJwtValidator {
private static final String ISSUER = "my-app";
private static final String AUDIENCE = "my-api";
public Claims validateJwtFully(String token, String secret) {
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(
Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8))
)
.requireIssuer(ISSUER) // Проверить издателя
.requireAudience(AUDIENCE) // Проверить аудиторию
.build()
.parseClaimsJws(token)
.getBody();
// Дополнительная проверка истечения (обычно встроена)
if (claims.getExpiration() != null) {
if (claims.getExpiration().before(new Date())) {
throw new IllegalArgumentException("Токен истёк");
}
}
return claims;
} catch (Exception e) {
throw new SecurityException("Токен невалиден: " + e.getMessage(), e);
}
}
}
Пример JWT с полным контролем
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.nio.charset.StandardCharsets;
import java.util.Date;
public class JwtTokenGenerator {
public String generateToken(String userId, String secret) {
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
Date expirationDate = new Date(nowMillis + 3600000); // 1 час
return Jwts.builder()
.setSubject(userId)
.setIssuedAt(now)
.setExpiration(expirationDate)
.claim("iss", "my-app") // Издатель
.claim("aud", "my-api") // Аудитория
.claim("role", "user") // Роль
.signWith(
Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)),
SignatureAlgorithm.HS256
)
.compact();
}
}
Полный цикл валидации в Spring Security
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.stereotype.Component;
@Component
public class JwtAuthenticationValidator {
private final JwtDecoder jwtDecoder;
public JwtAuthenticationValidator(JwtDecoder jwtDecoder) {
this.jwtDecoder = jwtDecoder;
}
public Authentication validateAndGetAuthentication(String token) {
try {
// JwtDecoder автоматически проверяет:
// 1. Подпись
// 2. Expiration (exp)
// 3. Issued At (iat)
// 4. Not Before (nbf)
Jwt jwt = jwtDecoder.decode(token);
// Дополнительные проверки
String issuer = jwt.getIssuer().toString();
String audience = jwt.getAudience().toString();
if (!issuer.equals("https://auth-server.example.com")) {
throw new SecurityException("Неправильный издатель");
}
if (!audience.contains("my-api")) {
throw new SecurityException("Неправильная аудитория");
}
// Вернуть авторизацию
return createAuthentication(jwt);
} catch (Exception e) {
throw new SecurityException("Валидация токена не пройдена", e);
}
}
private Authentication createAuthentication(Jwt jwt) {
// Извлечь роли и создать Authentication
// ...
return null;
}
}
Конфигурация Spring Security для JWT
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
@Configuration
public class SecurityConfiguration {
@Bean
public JwtDecoder jwtDecoder() {
NimbusJwtDecoder decoder = NimbusJwtDecoder
.withJwkSetUri("https://auth-server.example.com/.well-known/jwks.json")
.build();
// Требовать проверку издателя
decoder.getJwtClaimsSetVerifier().setRequireAudience(true);
return decoder;
}
}
Типичные ошибки при валидации JWT
Ошибка 1: Игнорирование expiration
// ПЛОХО
if (isSignatureValid(token)) {
// Не проверяется exp!
return true;
}
Ошибка 2: Жёсткое кодирование secret в коде
// ПЛОХО
private static final String SECRET = "my-super-secret-key";
// ХОРОШО
private final String secret; // Из конфигурации/environment
Ошибка 3: Не проверять kid (Key ID)
// Если используются несколько ключей (key rotation)
public String getSigningKeyForAlgorithm(String kid) {
// Найти правильный ключ по ID
return signingKeys.get(kid);
}
Проверочный список валидации JWT
- Подпись — проверяется алгоритм и ключ
- Expiration (exp) — токен не должен быть истёкшим
- Issued At (iat) — убедиться, что токен выдан в разумное время
- Not Before (nbf) — токен не должен использоваться раньше этого времени
- Issuer (iss) — токен от доверенного издателя
- Audience (aud) — токен предназначен для нашего сервиса
- Subject (sub) — пользователь существует в системе
- Custom claims — проверить роли, разрешения и т.д.
Заключение
Прямой ответ: Да, технически можно проверить только подпись JWT. Однако в production это критическая уязвимость безопасности.
Опытный Java разработчик всегда:
- Проверяет все обязательные поля JWT
- Использует готовые библиотеки (io.jsonwebtoken, Spring Security)
- Понимает последствия каждого параметра
- Реализует проверку expiration, issuer и audience
- Использует современные стандарты OAuth 2.0 / OpenID Connect
Это фундаментальная компетенция для разработки безопасных приложений.