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

Как правильно сделать функцию logout при использовании JWT-токена?

2.0 Middle🔥 191 комментариев
#Python Core#Soft Skills

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

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

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

Логаут при использовании JWT

Квадратура этого круга состоит в том, что JWT самодостаточный токен и сервер его не хранит. После выдачи токена клиенту, сервер не может отозвать его напрямую из-за stateless архитектуры. Приходится проделать несколько шагов, чтобы логаут работал правильно.

Стандартные подходы

1. Blacklist токенов (На уровне памяти или Redis)

При логауте добавляем JWT в чёрный список:

import redis
from datetime import datetime
from typing import Optional

redis_client = redis.Redis(host="localhost", port=6379, decode_responses=True)

def logout(token: str, exp_timestamp: int) -> None:
    """Добавить JWT в чёрный список."""
    ttl = exp_timestamp - int(datetime.utcnow().timestamp())
    if ttl > 0:
        redis_client.setex(f"blacklist:{token}", ttl, "revoked")

def is_token_blacklisted(token: str) -> bool:
    """Проверить, находится ли токен в чёрном списке."""
    return redis_client.exists(f"blacklist:{token}") > 0

Плюсы:

  • Простая реализация
  • Немедленная инвалидация
  • TTL автоматически удаляет из Redis

Минусы:

  • Дополнительное хранилище (Redis)
  • Утечка памяти без Redis

2. Refresh Token Rotation

Хранить только refresh токены, JWT используются как short-lived:

from datetime import datetime, timedelta
import jwt
from typing import Dict, Tuple

SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"

class TokenManager:
    def __init__(self, db):
        self.db = db  # база данных
    
    def create_tokens(self, user_id: str) -> Tuple[str, str]:
        """Создать пару access и refresh токенов."""
        access_payload = {
            "sub": user_id,
            "type": "access",
            "exp": datetime.utcnow() + timedelta(minutes=15),
            "iat": datetime.utcnow(),
        }
        refresh_payload = {
            "sub": user_id,
            "type": "refresh",
            "exp": datetime.utcnow() + timedelta(days=7),
            "iat": datetime.utcnow(),
        }
        
        access_token = jwt.encode(access_payload, SECRET_KEY, algorithm=ALGORITHM)
        refresh_token = jwt.encode(refresh_payload, SECRET_KEY, algorithm=ALGORITHM)
        
        # Сохраняем refresh в базе для отслеживания
        self.db.store_refresh_token(user_id, refresh_token, refresh_payload["exp"])
        
        return access_token, refresh_token
    
    def logout(self, user_id: str, refresh_token: str) -> None:
        """Инвалидировать refresh токен."""
        # Удаляем refresh из базы
        self.db.delete_refresh_token(user_id, refresh_token)
    
    def verify_refresh(self, refresh_token: str) -> Dict:
        """Проверить refresh токен и выдать новую пару."""
        payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=[ALGORITHM])
        
        # Проверяем наличие в базе
        if not self.db.refresh_token_exists(payload["sub"], refresh_token):
            raise Exception("Refresh token revoked")
        
        # Создаём новую пару
        return self.create_tokens(payload["sub"])

Плюсы:

  • Нет нужды в Redis
  • Безопасно: долгоживущие refresh хранятся в БД
  • Можно иметь несколько активных сессий

Минусы:

  • Сложнее логика
  • Нужно сохранять refresh в БД

3. Комбинированный подход

Для критичных случаев (смена пароля, хак аккаунта):

def logout_all_sessions(user_id: str) -> None:
    """Инвалидировать все сессии пользователя."""
    # Удаляем все refresh токены
    self.db.delete_all_refresh_tokens(user_id)
    
    # И добавляем в blacklist все активные access токены
    active_tokens = self.db.get_user_active_tokens(user_id)
    for token in active_tokens:
        redis_client.setex(f"blacklist:{token}", 900, "revoked")  # 15 мин

Лучшая практика

Рекомендую использовать Refresh Token Rotation:

  1. Access Token (JWT) — short-lived (15 мин), не хранится на сервере
  2. Refresh Token — хранится в БД с TTL, инвалидируется при логауте
  3. Логаут — просто удаляем refresh из БД
  4. Автоматическое обновление — клиент получает новую пару при exp access

Такой подход масштабируется, безопасен и не требует Redis для всех операций.

Как правильно сделать функцию logout при использовании JWT-токена? | PrepBro