You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DRF结合Simple JWT的Token自动刷新中间件失效问题求助

问题:Access Token过期后首次请求返回401,二次刷新才正常?

我使用Django(dj-rest-auth)实现认证功能,将access token和refresh token存储在httpOnly cookie中,前端无法直接访问这些令牌,因此需要在后端处理令牌刷新逻辑。编写了如下中间件用于自动刷新过期的access token:

from datetime import datetime, timezone
from rest_framework_simplejwt.tokens import RefreshToken
from django.http import HttpResponse
from django.utils.deprecation import MiddlewareMixin

class TokenRefreshMiddleware(MiddlewareMixin):
    def process_response(self, request, response):
        if response.status_code == 200:
            refresh_token = request.COOKIES.get('refresh_token')
            if refresh_token:
                try:
                    decoded = RefreshToken(refresh_token, verify=False)
                    if decoded:
                        if decoded.access_token['exp'] < datetime.now(timezone.utc).timestamp():
                            refresh = RefreshToken(refresh_token)
                            response.set_cookie('access_token', refresh.access_token)
                            response.set_cookie('refresh_token', refresh)
                except:
                    pass
        if response.status_code == 401:
            refresh_token = request.COOKIES.get('refresh_token')
            if refresh_token:
                print("refresh_token = = = = = ", refresh_token)
                try:
                    decoded = RefreshToken(refresh_token, verify=False)
                    if decoded:
                            refresh = RefreshToken(refresh_token)
                            response.set_cookie('access_token', refresh.access_token)
                            response.set_cookie('refresh_token', refresh)
                            response.status_code = 200
                            
                except:
                    pass
        return response

但当access token过期时,会收到如下错误响应:

{
"detail": "Given token not valid for any token type",
"code": "token_not_valid",
"messages": [
{
"token_class": "AccessToken",
"token_type": "access",
"message": "Token is invalid or expired"
}
]
}

只有第二次刷新页面后,已认证的用户信息才会正常显示,请问该如何解决?


解决方案

核心问题:中间件执行时机错误

你当前的中间件用了process_response,这个方法是在视图处理完成、已经生成响应之后才执行的。也就是说,第一次请求时,视图已经因为无效的access token返回了401,你才去刷新token并修改响应状态码,但此时视图返回的错误内容已经无法改变,所以前端还是会收到401。第二次请求时,新的token已经存在cookie里,所以能正常认证。

正确的做法是在视图处理之前检查token有效性并刷新,也就是用process_request方法。

修复后的中间件代码

from datetime import datetime, timezone
from rest_framework_simplejwt.tokens import RefreshToken, AccessToken
from django.utils.deprecation import MiddlewareMixin
from rest_framework_simplejwt.exceptions import TokenError

class TokenRefreshMiddleware(MiddlewareMixin):
    def process_request(self, request):
        # 尝试获取当前的access token
        access_token = request.COOKIES.get('access_token')
        refresh_token = request.COOKIES.get('refresh_token')
        
        if access_token:
            try:
                # 验证access token是否有效(自动检查过期)
                AccessToken(access_token)
                # token有效,直接跳过后续逻辑
                return None
            except TokenError:
                # access token无效/过期,进入刷新流程
                pass
        
        # 用refresh token尝试刷新
        if refresh_token:
            try:
                refresh = RefreshToken(refresh_token)
                # 生成新的access和refresh token
                new_access_token = str(refresh.access_token)
                new_refresh_token = str(refresh)
                
                # 更新请求的Authorization头,让后续认证流程能拿到有效token
                request.META['HTTP_AUTHORIZATION'] = f'Bearer {new_access_token}'
                
                # 把新token暂存到请求对象,后续在响应阶段设置cookie
                request._new_tokens = (new_access_token, new_refresh_token)
            except TokenError:
                # refresh token也无效,标记需要清除cookie
                request._clear_tokens = True
        
        return None
    
    def process_response(self, request, response):
        # 在响应阶段设置新的cookie
        if hasattr(request, '_new_tokens'):
            access_token, refresh_token = request._new_tokens
            # 根据你的实际需求配置cookie属性(httponly、secure、samesite等)
            response.set_cookie(
                'access_token',
                access_token,
                httponly=True,
                secure=True,
                samesite='Lax',
                max_age=3600  # 对应access token的过期时间(1小时)
            )
            response.set_cookie(
                'refresh_token',
                refresh_token,
                httponly=True,
                secure=True,
                samesite='Lax',
                max_age=7*24*3600  # 对应refresh token的过期时间(7天)
            )
        
        # 如果refresh token无效,清除cookie
        if hasattr(request, '_clear_tokens'):
            response.delete_cookie('access_token')
            response.delete_cookie('refresh_token')
        
        return response

关键改进点

  1. 提前处理token刷新:在process_request中先检查access token有效性,无效则立即用refresh token刷新,并把新token注入到请求头中,确保后续视图处理时能通过认证。
  2. 安全验证token:不再用verify=False跳过验证,而是通过捕获TokenError异常来判断token是否有效,避免安全风险。
  3. 分离逻辑:把token刷新逻辑放在请求阶段,cookie设置放在响应阶段,符合Django中间件的执行流程。
  4. 处理refresh token无效场景:当refresh token也过期时,自动清除cookie,避免无效请求循环。

额外注意事项

  • 确保你的认证类(比如JWTAuthentication)是从Authorization头获取token的,这样修改request.META['HTTP_AUTHORIZATION']才会生效。
  • 生产环境中务必开启secure=True(仅HTTPS下传输cookie),并根据需求设置samesite属性,提升安全性。
  • 调整max_age值,使其与你在simplejwt配置中设置的token过期时间一致。

内容的提问来源于stack exchange,提问作者Bif_Titan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 13:10:58