← Назад к вопросам
Как правильно сделать функцию 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:
- Access Token (JWT) — short-lived (15 мин), не хранится на сервере
- Refresh Token — хранится в БД с TTL, инвалидируется при логауте
- Логаут — просто удаляем refresh из БД
- Автоматическое обновление — клиент получает новую пару при exp access
Такой подход масштабируется, безопасен и не требует Redis для всех операций.