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

55 lines
2.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()