使用rest_framework_simplejwt时自定义JWT加密中间件不生效排查
问题分析与解决方案
核心问题:中间件未正确修改响应内容
你的中间件逻辑本身没问题,但忽略了DRF Response对象的渲染缓存机制——当Response的content属性被访问过(比如某个上游中间件提前渲染了响应),后续修改response.data不会自动更新返回给客户端的内容,导致明文token依然被返回。
修复步骤
1. 让中间件修改后重新渲染响应
在删除response.data中的access和refresh后,添加代码清空缓存并重新渲染:
# 清空已缓存的响应内容,触发重新渲染 response.content = None response.render()
修改后的中间件关键代码段:
del response.data["access"] del response.data["refresh"] # 新增:清空缓存并重新渲染 response.content = None response.render() response.set_cookie( "access_token", encrypted_access_token, httponly=True, secure=True, samesite="Strict", ) response.set_cookie( "refresh_token", encrypted_refresh_token, httponly=True, secure=True, samesite="Strict", )
2. 验证中间件是否被正确触发
在process_response方法中添加打印日志,确认代码是否进入了token处理分支:
def process_response(self, request, response): print("Token加密中间件已调用") if response.status_code == 200 and hasattr(response, 'data'): print("检测到DRF Response对象") if "access" in response.data and "refresh" in response.data: print("开始处理JWT令牌") # 你的加密逻辑... return response
如果没有打印对应日志,检查MIDDLEWARE配置中的中间件路径是否正确。
3. 更可靠的方案:自定义Token视图替代中间件
中间件会处理所有HTTP响应,容易误触发。更精准的方式是重写TokenObtainPairView,直接在登录视图中处理加密和Cookie设置:
from rest_framework_simplejwt.views import TokenObtainPairView from cryptography.fernet import Fernet from django.conf import settings from rest_framework.response import Response class CustomTokenObtainPairView(TokenObtainPairView): def post(self, request, *args, **kwargs): response = super().post(request, *args, **kwargs) if response.status_code == 200: # 用合法Fernet密钥初始化加密器 cipher = Fernet(settings.JWT_KEY.encode()) encrypted_access = cipher.encrypt(response.data["access"].encode()).decode() encrypted_refresh = cipher.encrypt(response.data["refresh"].encode()).decode() # 设置Cookie,适配开发/生产环境 response.set_cookie( "access_token", encrypted_access, httponly=True, secure=not settings.DEBUG, samesite="Strict", ) response.set_cookie( "refresh_token", encrypted_refresh, httponly=True, secure=not settings.DEBUG, samesite="Strict", ) # 清空响应数据,不返回明文token response.data = {} response.content = None response.render() return response
然后在urls.py中替换默认路由:
from django.urls import path from .views import CustomTokenObtainPairView urlpatterns = [ path('token/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'), # 其他路由 ]
加密逻辑的修正
你当前的密钥处理方式存在错误:Fernet要求密钥是urlsafe base64编码的32字节原始密钥。正确的做法是:
- 在Django Shell中生成合法密钥:
from cryptography.fernet import Fernet print(Fernet.generate_key().decode())
- 将生成的字符串存入
settings.py:
JWT_KEY = "生成的Fernet密钥字符串"
- 在代码中直接使用:
cipher = Fernet(settings.JWT_KEY.encode())
rest_framework_simplejwt与自定义中间件的交互注意事项
- 响应渲染时机:DRF Response的
content属性被访问后会缓存渲染结果,修改data后必须清空缓存并重新渲染,否则修改不会生效。 - 中间件范围控制:中间件会拦截所有响应,必须通过精准的条件判断(比如检查请求路径是否为token接口)避免误处理其他响应。
- Token视图的默认行为:
TokenObtainPairView默认返回明文token,必须确保在响应发送前清空这些字段,无论是在视图还是中间件中。 - Cookie属性适配环境:
secure=True仅在HTTPS环境下有效,开发环境建议用not settings.DEBUG动态设置,否则Cookie无法被浏览器保存。
内容的提问来源于stack exchange,提问作者andy_H
相关产品推荐
相关产品推荐

