Files
medical_training/apps/user/auth/refresh.py
T

55 lines
2.2 KiB
Python
Raw Normal View History

2026-05-29 15:58:00 +08:00
from rest_framework.permissions import AllowAny
from rest_framework_simplejwt.views import TokenRefreshView
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework_simplejwt.exceptions import TokenError
from drf_spectacular.utils import extend_schema
from config.exceptions import AppError
from apps.user.utils.jwt_redis import revoke_token, is_token_revoked, get_user_invalid_before
@extend_schema(tags=['认证'])
class CustomTokenRefreshView(TokenRefreshView):
"""U8 刷新 Token — 在 simplejwt 旋转前后加入 Redis 黑名单检查 + 旧 token 吊销"""
permission_classes = [AllowAny]
authentication_classes = ()
def post(self, request, *args, **kwargs):
refresh_raw = request.data.get('refresh', '')
if not refresh_raw:
raise AppError('AUTH_TOKEN_INVALID', '请提供 refresh token', status_code=401)
# ── 解析旧 token(必须在 super().post() 之前,因为 simplejwt 会 mutate ──
try:
old_token = RefreshToken(refresh_raw)
except TokenError:
raise AppError('AUTH_TOKEN_INVALID', 'refresh token 无效或已过期', status_code=401)
old_jti = old_token.payload.get('jti')
old_exp = old_token.payload.get('exp')
uid = old_token.payload.get('user_id')
iat = old_token.payload.get('iat')
# ── Redis 黑名单检查 ──
if old_jti and is_token_revoked(old_jti):
raise AppError('AUTH_TOKEN_INVALID', 'refresh token 已被吊销', status_code=401)
# ── 用户级失效截止检查 ──
if uid and iat is not None:
invalid_before = get_user_invalid_before(uid)
if invalid_before is not None and iat < invalid_before:
raise AppError('AUTH_TOKEN_INVALID', 'token 已失效,请重新登录', status_code=401)
# ── 交给 simplejwt 处理旋转 ──
response = super().post(request, *args, **kwargs)
# ── 吊销旧 refresh token ──
if old_jti and old_exp:
revoke_token(old_jti, old_exp)
return response
# 函数式引用,供 urls.py 保持一致风格
refresh_token = CustomTokenRefreshView.as_view()