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

Каким образом Backend определяет поддельный JWT?

2.0 Middle🔥 151 комментариев
#REST API и HTTP#Безопасность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Каким образом Backend определяет поддельный JWT?

JWT (JSON Web Token) — это подписанный токен, и его подлинность определяется через криптографическую проверку подписи.

1. Основной механизм: криптографическая подпись

JWT состоит из 3 частей: header.payload.signature

import jwt

class JWTValidator:
    def __init__(self, secret_key: str):
        self.secret_key = secret_key
    
    def verify_jwt(self, token: str) -> dict:
        try:
            payload = jwt.decode(
                token,
                self.secret_key,
                algorithms=['HS256']
            )
            return payload
        except jwt.InvalidSignatureError:
            raise ValueError("Invalid JWT signature - FAKE TOKEN")
        except jwt.ExpiredSignatureError:
            raise ValueError("JWT expired")

validator = JWTValidator("my-secret-key")
payload = validator.verify_jwt("eyJhbGc...")

Когда backend получает JWT:

  1. Расчитываем ожидаемую подпись HMAC(header + payload, secret)
  2. Сравниваем с полученной подписью
  3. Если не совпадает → поддельный JWT

2. Как подпись работает

import hmac
import hashlib
import json
import base64

def decode_jwt_manually(token: str, secret: str) -> bool:
    header_b64, payload_b64, signature_b64 = token.split('.')
    
    # Ожидаемая подпись
    message = f"{header_b64}.{payload_b64}".encode('utf-8')
    expected_signature = hmac.new(
        secret.encode('utf-8'),
        message,
        hashlib.sha256
    ).digest()
    
    # Полученная подпись
    signature_b64 += '=' * (4 - len(signature_b64) % 4)
    received_signature = base64.urlsafe_b64decode(signature_b64)
    
    # Сравнение
    is_valid = hmac.compare_digest(expected_signature, received_signature)
    return is_valid

3. Частые атаки и защита

Атака 1: Algorithm confusion

# ❌ УЯЗВИМО
payload = jwt.decode(token, secret_key)  # Без algorithms!

# ✅ ЗАЩИТА
payload = jwt.decode(
    token,
    secret_key,
    algorithms=['HS256']  # Жёсткое ограничение!
)

Атака 2: Слабый secret

# ❌ УЯЗВИМО
secret = "password123"  # Легко угадать!

# ✅ ЗАЩИТА
import secrets
secret = secrets.token_urlsafe(32)

4. Полная проверка JWT

from datetime import datetime, timedelta

class ComprehensiveJWTValidator:
    def __init__(self, secret_key: str):
        self.secret_key = secret_key
        self.blacklist = set()  # Для revoked tokens
    
    def verify_jwt_comprehensive(self, token: str) -> dict:
        try:
            # 1. Проверяем подпись
            payload = jwt.decode(
                token,
                self.secret_key,
                algorithms=['HS256']
            )
            
            # 2. Проверяем expiration
            if 'exp' in payload:
                exp_timestamp = payload['exp']
                if exp_timestamp < datetime.utcnow().timestamp():
                    raise ValueError("Token expired")
            
            # 3. Проверяем not-before (iat)
            if 'iat' in payload:
                iat_timestamp = payload['iat']
                if iat_timestamp > datetime.utcnow().timestamp():
                    raise ValueError("Token not yet valid")
            
            # 4. Проверяем blacklist (revocation)
            if token in self.blacklist:
                raise ValueError("Token has been revoked")
            
            # 5. Проверяем обязательные поля
            required_fields = ['user_id', 'exp', 'iat']
            if not all(field in payload for field in required_fields):
                raise ValueError("Missing required fields")
            
            return payload
        except jwt.InvalidSignatureError:
            raise ValueError("JWT signature is invalid")
    
    def create_jwt(self, user_id: int, expires_in_hours: int = 24) -> str:
        now = datetime.utcnow()
        payload = {
            'user_id': user_id,
            'iat': int(now.timestamp()),
            'exp': int((now + timedelta(hours=expires_in_hours)).timestamp())
        }
        return jwt.encode(payload, self.secret_key, algorithm='HS256')

5. Использование в FastAPI

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthCredentials

app = FastAPI()
security = HTTPBearer()
jwt_validator = ComprehensiveJWTValidator("secret-key")

def verify_token(credentials: HTTPAuthCredentials) -> dict:
    try:
        return jwt_validator.verify_jwt_comprehensive(credentials.credentials)
    except ValueError as e:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=str(e)
        )

@app.post("/login")
async def login(username: str, password: str):
    # Проверяем username и password
    token = jwt_validator.create_jwt(user_id=123)
    return {"access_token": token, "token_type": "bearer"}

@app.get("/protected")
async def protected_route(payload: dict = Depends(verify_token)):
    return {"message": f"Hello, user {payload['user_id']}"}

6. Ключевые моменты

Как определяется поддельный JWT:

  1. ❌ Подпись не совпадает с ожидаемой
  2. ❌ Токен истёк (exp < now)
  3. ❌ Токен в чёрном списке (revoked)
  4. ❌ Отсутствуют обязательные поля
  5. ❌ Алгоритм не из допустимых

Защита:

  • ✅ Секретный ключ > 32 символов
  • ✅ Явно указываем algorithms=['HS256']
  • ✅ Проверяем exp, iat, blacklist
  • ✅ Используем HTTPS (не HTTP)
  • ✅ Не хранишь sensitive данные в payload

Итог: Backend определяет поддельный JWT через криптографическую проверку подписи. Если подпись не совпадает — JWT поддельный. Дополнительно проверяются expiration, blacklist, обязательные поля и типы данных.

Каким образом Backend определяет поддельный JWT? | PrepBro