← Назад к вопросам

Нужно ли расшифровывать JSON Web Token?

1.7 Middle🔥 191 комментариев
#Безопасность

Комментарии (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);
}

Выводы

  1. JWT НЕ шифруется, он кодируется (Base64) — Payload видно всем
  2. Payload ВСЕГДА может быть декодирован — это нормально
  3. Защита — в подписи — только сервер может подделать
  4. ОБЯЗАТЕЛЬНО проверяй подпись перед использованием данных
  5. Используй HMAC или RSA для подписи
  6. Никогда не кэшируй чувствительные данные в JWT
  7. Устанавливай TTL (время истечения) для токенов
  8. Используй HTTPS для передачи JWT (запретить просмотр в пути)
Нужно ли расшифровывать JSON Web Token? | PrepBro