Django DRF Simple JWT如何实现access token过期时跳转refresh token视图
实现步骤说明
首先先修复现有代码的基础问题,再根据你的场景选择自动刷新方案:
第一步:修复现有代码错误
1. 补充LoginView的refresh token存储
你当前的登录逻辑只存了access token到Cookie,没有存refresh token,无法执行刷新操作,修改LoginView代码:
class LoginView(APIView): def post(self, request, format=None): data = request.data response = Response() email = data.get('email', None) password = data.get('password', None) user = authenticate(email=email, password=password) if user is not None: if user.is_active: data = get_tokens_for_user(user) # 存储access token response.set_cookie( key = settings.SIMPLE_JWT['AUTH_COOKIE'], value = data["access"], expires = settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'], secure = settings.SIMPLE_JWT['AUTH_COOKIE_SECURE'], httponly = settings.SIMPLE_JWT['AUTH_COOKIE_HTTP_ONLY'], samesite = settings.SIMPLE_JWT['AUTH_COOKIE_SAMESITE'] ) # 新增:存储refresh token response.set_cookie( key = "refresh_token", value = data["refresh"], expires = settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'], secure = settings.SIMPLE_JWT['AUTH_COOKIE_SECURE'], httponly = settings.SIMPLE_JWT['AUTH_COOKIE_HTTP_ONLY'], samesite = settings.SIMPLE_JWT['AUTH_COOKIE_SAMESITE'] ) csrf.get_token(request) response.data = {"Success" : "Login successfully","data":data} return response else: return Response({"No active" : "This account is not active!!"}, status=status.HTTP_404_NOT_FOUND) else: return Response({"Invalid" : "Invalid email or password!!"}, status=status.HTTP_404_NOT_FOUND)
2. 修正settings.py的认证类配置
你当前配置用的是默认JWT认证类,要替换成你自定义的CustomAuthentication:
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'api.authenticate.CustomAuthentication', # 修改为自定义认证类路径 ), 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', # 需认证的接口默认用这个,公开接口单独加AllowAny ), }
3. 适配Simple JWT的刷新逻辑
你当前urls.py里的代码是老版本JWT库的逻辑,和你用的Simple JWT不兼容,删除旧代码,自定义适配Cookie的刷新视图:
在views.py新增刷新视图:
from rest_framework_simplejwt.views import TokenRefreshView from rest_framework_simplejwt.exceptions import InvalidToken, TokenError class CookieTokenRefreshView(TokenRefreshView): def post(self, request, *args, **kwargs): # 从Cookie读取refresh token refresh_token = request.COOKIES.get('refresh_token') if not refresh_token: return Response({"detail": "Refresh token缺失"}, status=status.HTTP_401_UNAUTHORIZED) request.data['refresh'] = refresh_token serializer = self.get_serializer(data=request.data) try: serializer.is_valid(raise_exception=True) except TokenError as e: raise InvalidToken(e.args[0]) # 刷新成功后更新access token的Cookie response = Response(serializer.validated_data, status=status.HTTP_200_OK) response.set_cookie( key = settings.SIMPLE_JWT['AUTH_COOKIE'], value = serializer.validated_data["access"], expires = settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'], secure = settings.SIMPLE_JWT['AUTH_COOKIE_SECURE'], httponly = settings.SIMPLE_JWT['AUTH_COOKIE_HTTP_ONLY'], samesite = settings.SIMPLE_JWT['AUTH_COOKIE_SAMESITE'] ) # 如果开启了refresh token轮转,同步更新refresh token的Cookie if settings.SIMPLE_JWT.get('ROTATE_REFRESH_TOKENS', False): response.set_cookie( key = 'refresh_token', value = serializer.validated_data["refresh"], expires = settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'], secure = settings.SIMPLE_JWT['AUTH_COOKIE_SECURE'], httponly = settings.SIMPLE_JWT['AUTH_COOKIE_HTTP_ONLY'], samesite = settings.SIMPLE_JWT['AUTH_COOKIE_SAMESITE'] ) return response
修改urls.py的刷新路径配置:
urlpatterns = [ path('login/', views.LoginView.as_view(), name="login"), path('refresh/',views.CookieTokenRefreshView.as_view(), name="refresh"), ]
第二步:选择自动刷新方案
不建议直接重定向到refresh视图,因为refresh接口需要POST请求,普通302重定向会转为GET请求无法正常刷新,推荐以下两种方案:
方案1:前端配合实现(前后端分离场景推荐)
Simple JWT默认在access token过期时会返回401状态码,错误标识为token_not_valid,前端捕获到该响应时,自动发起POST请求到/refresh/接口,拿到新的access token后重新发起原来的发布帖子请求即可。如果刷新接口也返回401,说明refresh token也已过期,再跳转登录页。
方案2:后端中间件自动刷新(用户无感知)
自定义中间件自动捕获token过期异常,内部完成刷新后继续处理原请求:
新建middleware.py:
from rest_framework_simplejwt.exceptions import TokenExpiredError from django.conf import settings import json class AutoRefreshTokenMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): response = self.get_response(request) # 检查是否是access token过期的401响应 if response.status_code == 401: try: content = json.loads(response.content) if content.get('code') == 'token_not_valid' and 'expired' in content.get('detail', ''): # 构造刷新请求 refresh_request = request.__class__(request.META) refresh_request.method = 'POST' refresh_request.data = {} refresh_response = views.CookieTokenRefreshView.as_view()(refresh_request) if refresh_response.status_code == 200: # 刷新成功,更新请求中的access token,重新处理原请求 request.COOKIES[settings.SIMPLE_JWT['AUTH_COOKIE']] = refresh_response.data['access'] new_response = self.get_response(request) # 同步新的Cookie到响应 for k, v in refresh_response.cookies.items(): new_response.set_cookie( k, v.value, expires=v['expires'], secure=v['secure'], httponly=v['httponly'], samesite=v['samesite'], path=v['path'] ) return new_response except: # 刷新失败直接返回原401响应 pass return response
把中间件加到settings.py的MIDDLEWARE列表中:
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'api.middleware.AutoRefreshTokenMiddleware', # 新增到这个位置 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', # 剩余原有中间件保持不变 ]
这个方案前端不需要做任何修改,刷新过程用户完全无感知。
内容的提问来源于stack exchange,提问作者Zero0
相关产品推荐
相关产品推荐

