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

Django DRF认证方法中调用视图刷新JWT令牌报错及替代方案咨询

在Django+DRF中处理Access Token过期自动刷新的解决方案

首先明确:完全可以在Access Token过期时签发新的Access Token,你遇到的循环导入错误是代码结构问题,而非功能本身不可行。以下是几种可行的解决方案:

方案1:延迟导入视图类

不要在自定义认证类的文件顶部导入CookieTokenRefreshView,而是在authenticate方法内部需要使用时再导入,避开初始化阶段的循环依赖:

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
# 不要在这里导入CookieTokenRefreshView

class CustomAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # 1. 先验证当前Access Token
        access_token = request.COOKIES.get('access_token')
        if not access_token:
            return None
        
        # 2. 判断Access Token是否过期(这里需要自己实现过期校验逻辑)
        is_token_expired = self._is_access_token_expired(access_token)
        if not is_token_expired:
            # 正常认证流程,解析token返回用户
            user = self._get_user_from_token(access_token)
            return (user, access_token)
        
        # 3. 若过期,延迟导入刷新视图并处理
        try:
            from account.views import CookieTokenRefreshView
            # 构造刷新请求,从Cookie获取Refresh Token
            refresh_token = request.COOKIES.get('refresh_token')
            if not refresh_token:
                raise AuthenticationFailed("Refresh token not found")
            
            # 调用刷新视图的逻辑
            refresh_request = request.copy()
            refresh_request.data = {'refresh': refresh_token}
            refresh_response = CookieTokenRefreshView.as_view()(refresh_request)
            
            if refresh_response.status_code != 200:
                raise AuthenticationFailed("Failed to refresh token")
            
            # 获取新的Access Token,更新请求Cookie并继续认证
            new_access_token = refresh_response.data.get('access')
            request.COOKIES['access_token'] = new_access_token
            user = self._get_user_from_token(new_access_token)
            return (user, new_access_token)
        except ImportError:
            raise AuthenticationFailed("Token refresh service unavailable")

    def _is_access_token_expired(self, token):
        # 实现Token过期校验逻辑,比如用jwt库解析exp字段
        import jwt
        from datetime import datetime
        try:
            payload = jwt.decode(token, options={"verify_signature": False})
            exp_timestamp = payload.get('exp')
            return datetime.utcnow().timestamp() > exp_timestamp
        except:
            return True
    
    def _get_user_from_token(self, token):
        # 实现从Token中解析用户的逻辑
        # ... 省略具体实现
        return user

方案2:抽离刷新逻辑为独立工具函数

将CookieTokenRefreshView中刷新Token的核心逻辑抽离到单独的工具模块,让认证类和视图都依赖这个工具,避免循环依赖:

1. 创建工具模块account/utils.py

from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError

def refresh_access_token(refresh_token):
    """根据Refresh Token生成新的Access Token"""
    try:
        refresh = RefreshToken(refresh_token)
        new_access = str(refresh.access_token)
        # 可以在这里添加自定义逻辑,比如更新Token的额外字段
        return new_access, refresh
    except (InvalidToken, TokenError):
        return None, None

2. 在自定义认证类中使用工具函数

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from account.utils import refresh_access_token

class CustomAuthentication(BaseAuthentication):
    def authenticate(self, request):
        access_token = request.COOKIES.get('access_token')
        if not access_token:
            return None
        
        # 校验Access Token是否过期
        if self._is_access_token_expired(access_token):
            refresh_token = request.COOKIES.get('refresh_token')
            new_access_token, _ = refresh_access_token(refresh_token)
            if not new_access_token:
                raise AuthenticationFailed("Invalid or expired refresh token")
            
            # 用新Token继续认证
            access_token = new_access_token
            # 标记需要在响应中更新Access Token Cookie
            request._update_access_cookie = new_access_token
        
        # 解析Token获取用户
        user = self._get_user_from_token(access_token)
        return (user, access_token)
    
    # 省略_is_access_token_expired和_get_user_from_token方法,同方案1

3. 在CookieTokenRefreshView中复用工具函数

from rest_framework_simplejwt.views import TokenRefreshView
from account.utils import refresh_access_token
from rest_framework import status
from rest_framework.response import Response

class CookieTokenRefreshView(TokenRefreshView):
    def post(self, request, *args, **kwargs):
        refresh_token = request.COOKIES.get('refresh_token') or request.data.get('refresh')
        new_access_token, refresh = refresh_access_token(refresh_token)
        
        if not new_access_token:
            return Response({"detail": "Invalid refresh token"}, status=status.HTTP_401_UNAUTHORIZED)
        
        response = super().post(request, *args, **kwargs)
        # 设置新的Access Token到Cookie
        response.set_cookie('access_token', new_access_token, httponly=True, secure=True)
        return response

方案3:使用中间件处理Token刷新

通过中间件在请求到达认证类之前自动检查并刷新Token,让认证类只负责正常的Token验证,完全解耦刷新逻辑:

1. 创建中间件account/middleware.py

from django.utils.deprecation import MiddlewareMixin
from account.utils import refresh_access_token

class TokenRefreshMiddleware(MiddlewareMixin):
    def process_request(self, request):
        # 从Header或Cookie获取Access Token
        auth_header = request.META.get('HTTP_AUTHORIZATION', '')
        access_token = auth_header.split(' ')[-1] if auth_header.startswith('Bearer ') else request.COOKIES.get('access_token')
        
        if access_token and self._is_token_expired(access_token):
            refresh_token = request.COOKIES.get('refresh_token')
            new_access_token, _ = refresh_access_token(refresh_token)
            
            if new_access_token:
                # 更新请求的Authorization Header
                request.META['HTTP_AUTHORIZATION'] = f'Bearer {new_access_token}'
                # 标记需要在响应中更新Cookie
                request._new_access_token = new_access_token
    
    def process_response(self, request, response):
        # 如果有新的Access Token,更新Cookie
        if hasattr(request, '_new_access_token'):
            response.set_cookie('access_token', request._new_access_token, httponly=True, secure=True)
        return response
    
    def _is_token_expired(self, token):
        # 实现Token过期校验逻辑,同方案1
        import jwt
        from datetime import datetime
        try:
            payload = jwt.decode(token, options={"verify_signature": False})
            return datetime.utcnow().timestamp() > payload['exp']
        except:
            return True

2. 在settings.py中注册中间件

MIDDLEWARE = [
    # ... 其他中间件(注意顺序,建议放在DRF认证中间件之前)
    'account.middleware.TokenRefreshMiddleware',
    'rest_framework.authentication.SessionAuthentication',
    # ...
]

总结

循环导入的根源是认证类与视图类在初始化阶段的相互依赖,通过延迟导入、抽离工具函数或中间件解耦这三种方式都可以解决问题。其中抽离工具函数的方式最利于代码复用和维护,中间件方式则适合需要全局自动处理Token刷新的场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 05:22:52