禁用POST请求CSRF防护后仅单个视图抛出Forbidden (CSRF cookie not set.)
问题
我正在使用Django REST framework和simplejwt认证开发Django后端,为适配React Native移动应用已禁用视图的CSRF防护。但其他视图均正常工作,唯独自定义的Token黑名单视图抛出Forbidden (CSRF cookie not set.)错误。
正常工作的视图示例:
@method_decorator(csrf_exempt, name='dispatch') class BusinessProfileView(APIView): permission_classes = (permissions.IsAuthenticated, HasAccountOwnershipBP) def post(self, request, format='json'): serializer = BusinessProfileSerializer(data=request.data, context={'request': request}) if serializer.is_valid(): ... def put(self, request, format='json'): ...
抛出错误的Token黑名单视图:
@method_decorator(csrf_exempt, name='dispatch') class BlackListToken(APIView): permission_classes = (permissions.IsAuthenticated,) def post(self, request): refresh = request.data['refresh'] try: #get this refresh token from outstanding and invalidate it token = RefreshToken(refresh) token.blacklist() print('ran') return Response(status=status.HTTP_200_OK) except Exception as e: print(e) return Response(status=status.HTTP_400_BAD_REQUEST)
我尝试过直接在post方法上设置csrf_exempt但无效,最初使用simplejwt自带的黑名单视图也出现相同错误,因此改用自定义视图。附上中间件列表供排查:
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
请问为何仅该视图出现此问题,如何解决?
分析与解决
原因
- 认证流程触发CSRF检查:当使用
IsAuthenticated权限类时,Django的AuthenticationMiddleware会优先尝试通过session认证用户,而CsrfViewMiddleware会对session认证的请求强制CSRF检查——即使给视图加了csrf_exempt,如果认证过程先触发了session相关的CSRF验证,就会抛出错误。 - 装饰器优先级冲突:
@method_decorator(csrf_exempt, name='dispatch')理论上能豁免视图的CSRF检查,但如果simplejwt的认证逻辑与Django原生session认证存在冲突,或者装饰器没有正确覆盖视图的dispatch方法,可能导致豁免失效。
解决方法
方法一:直接设置APIView的csrf_exempt属性(推荐)
对于DRF的APIView,直接设置类属性csrf_exempt = True比装饰器更可靠,能确保整个视图跳过CSRF检查:
class BlackListToken(APIView): permission_classes = (permissions.IsAuthenticated,) csrf_exempt = True # 新增这个属性 def post(self, request): refresh = request.data['refresh'] try: token = RefreshToken(refresh) token.blacklist() return Response(status=status.HTTP_200_OK) except Exception as e: print(e) return Response(status=status.HTTP_400_BAD_REQUEST)
方法二:调整认证配置,避免session认证干扰
既然使用JWT认证,可在配置中明确DRF默认认证类为JWT,减少session认证的触发概率:
# settings.py REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_simplejwt.authentication.JWTAuthentication', ), } # 可选:如果不需要Django原生session认证,可调整认证后端 AUTHENTICATION_BACKENDS = [ 'django.contrib.auth.backends.ModelBackend', # 保留用于后台管理(按需选择) 'rest_framework_simplejwt.authentication.JWTAuthentication', ]
方法三:在路由层豁免CSRF检查
直接在urls.py中用csrf_exempt装饰视图的路由,确保请求到达视图前就跳过CSRF验证:
# urls.py from django.views.decorators.csrf import csrf_exempt from django.urls import path from .views import BlackListToken urlpatterns = [ path('token/blacklist/', csrf_exempt(BlackListToken.as_view()), name='blacklist-token'), # 其他路由... ]
内容的提问来源于stack exchange,提问作者Arham
相关产品推荐
相关产品推荐

