Django+React前端请求报CSRF token missing错误排查
- 报错内容:
CSRF Failed: CSRF token missing - 技术栈:Django + React
- 核心现象:前端控制台可正常打印获取到的csrftoken值,但后端无法识别前端请求携带的CSRF token;后端本地CSRF校验逻辑无异常,问题仅出现在前端发起的跨域请求场景。
后端视图(views.py)
class CheckAuthenticated(views.APIView): def get(self, request): if request.user.is_authenticated: return Response("Authenticated") else: return Response("Not Authenticated",status=401) class PostView(viewsets.ModelViewSet): serializer_class = serializer.PostSerializer def get_queryset(self): queryset = models.Post.objects.all() return queryset @method_decorator(ensure_csrf_cookie,csrf_protect) def create(self,request): authentication_classes = [SessionAuthentication] permissions_classes = [IsAuthenticated] post = serializer.PostSerializer(data=request.data) if post.is_valid(): title = post.data['title'] description = post.data['description'] models.Post.objects.create(title=title,description=description,user=User.objects.first()) return Response("post created successfully.") return Response("post creation failed.")
CSRF Token获取接口
class GetCSRFToken(views.APIView): permission_classes = [AllowAny, ] @method_decorator(ensure_csrf_cookie) def get(self, request, format=None): return Response("Success")
路由配置(urls.py)
urlpatterns = [ path('csrf/',views.GetCSRFToken.as_view(),name='csrf'), path('isauthenticated/',views.CheckAuthenticated.as_view(),name='authenticated'), ]
前端React实现代码
let handleSubmit = (e)=>{ e.preventDefault() console.log(Cookies.get('csrftoken')) axios.post('http://127.0.0.1:8000/posts/',post,{withCredentials:true},{headers:{'X-CSRFToken':Cookies.get('csrftoken')}}).then((res)=>{ console.log(res.data) }).catch((e)=>{ console.log(e.response.data) console.log(Cookies.get('csrftoken')) }) } useEffect(()=>{ axios.get('http://127.0.0.1:8000/posts/').then((res)=>{ setPostList(res.data) }) axios.get('http://127.0.0.1:8000/csrf/',{headers:{Authorization:null},withCredentials:true}) },[])
Django CSRF & 跨域配置
ALLOWED_HOSTS = ['*'] ACCESS_CONTROL_ALLOW_ORIGIN = '*' CORS_ORIGIN_ALLOW_ALL = True CORS_ALLOW_CREDENTIALS = True ACCESS_CONTROL_ALLOW_CREDENTIALS = True ACCESS_CONTROL_ALLOW_METHODS = '*' ACCESS_CONTROL_ALLOW_HEADERS = '*' ''' SESSION_COOKIE_SECURE = True CSRF_COOKIE_PATH = '/' ''' CSRF_COOKIE_SAMESITE = 'Strict' CSRF_TRUSTED_ORIGINS = [ "http://127.0.0.1:3000",'http://127.0.0.1:8000']
DRF认证配置
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.SessionAuthentication', ] }
- 前端调用
/csrf/接口后可正常读取到csrftoken的cookie值,cookie获取流程无异常 - 确认问题与DRF认证配置相关:使用
SessionAuthentication时触发CSRF missing报错,切换为BasicAuthentication后会出现已登录用户被判定为未授权的问题,暂未定位完整根因
问题由4处配置/代码写法错误共同导致,按以下顺序修复即可:
修正axios请求参数写法错误
axios.post方法仅支持3个参数:请求地址、请求体、请求配置。当前代码把withCredentials和headers拆成两个独立对象作为第3、第4个参数传入,实际post请求根本没有携带X-CSRFToken请求头,也没有正确开启跨域凭证携带。
错误写法:axios.post('http://127.0.0.1:8000/posts/',post,{withCredentials:true},{headers:{'X-CSRFToken':Cookies.get('csrftoken')}})修正后写法:
axios.post('http://127.0.0.1:8000/posts/', post, { withCredentials: true, headers: { 'X-CSRFToken': Cookies.get('csrftoken') } })调整CSRF Cookie的SameSite配置
当前设置CSRF_COOKIE_SAMESITE = 'Strict'时,跨端口(3000→8000)的跨站请求不会携带CSRF Cookie,后端无法从请求Cookie中读取到csrftoken值,自然无法和请求头中的token做比对校验。开发环境将该配置修改为Lax即可:CSRF_COOKIE_SAMESITE = 'Lax' # 生产环境如果部署在不同二级域名下,可设置为None,此时必须同时开启CSRF_COOKIE_SECURE = True(仅HTTPS环境生效)修正视图类的认证、权限写法
当前在create方法内部定义authentication_classes、permissions_classes是无效写法,类级别的配置需要定义在方法外层,否则csrf_protect装饰器无法获取正确的认证上下文,会判定token无效。同时序列化器校验通过后要从validated_data取值,关联用户直接取当前登录用户,不要硬取第一个用户。修正后的PostView代码:class PostView(viewsets.ModelViewSet): serializer_class = serializer.PostSerializer # 类属性直接定义在方法外层 authentication_classes = [SessionAuthentication] permission_classes = [IsAuthenticated] def get_queryset(self): return models.Post.objects.all() # csrf接口已经负责下发csrftoken cookie,这里不需要重复加ensure_csrf_cookie装饰器 @method_decorator(csrf_protect) def create(self,request): post = serializer.PostSerializer(data=request.data) if post.is_valid(): title = post.validated_data['title'] description = post.validated_data['description'] models.Post.objects.create(title=title, description=description, user=request.user) return Response("post created successfully.") return Response(post.errors, status=400)清理冲突的CORS配置
浏览器安全规则禁止跨域携带凭证(withCredentials=true)时将允许源设置为通配符*,当前手写的ACCESS_CONTROL_*开头的配置和django-cors-headers的配置存在冲突,直接删除所有ACCESS_CONTROL_*开头的自定义配置,调整CORS配置为明确指定允许源:# 删除所有ACCESS_CONTROL_ALLOW_*开头的配置 ALLOWED_HOSTS = ['*'] # 关闭全源允许,和凭证模式冲突 CORS_ORIGIN_ALLOW_ALL = False CORS_ALLOWED_ORIGINS = [ "http://127.0.0.1:3000", "http://127.0.0.1:8000", ] CORS_ALLOW_CREDENTIALS = True CORS_ALLOW_HEADERS = [ "accept", "accept-encoding", "authorization", "content-type", "dnt", "origin", "user-agent", "x-csrftoken", "x-requested-with", ] CSRF_COOKIE_SAMESITE = 'Lax' CSRF_TRUSTED_ORIGINS = [ "http://127.0.0.1:3000",'http://127.0.0.1:8000']
修复完成后重启Django服务,清空浏览器原有Cookie后重新访问页面,先调用/csrf/接口确认csrftoken Cookie的SameSite属性为Lax,再发起post请求即可正常通过CSRF校验,同时SessionAuthentication也能正常识别登录用户状态,不会出现未授权报错。
内容的提问来源于stack exchange,提问作者Ghazi

