React+Django生产环境Create接口CSRF Token异常求助
问题描述
开发环境中CSRF Cookie可正常设置,但生产环境调用Create接口创建帖子时,报错CSRF Failed: CSRF token from the 'X-Csrftoken' HTTP header has incorrect length.。登录、注册等其他POST请求正常,推测问题出在Django的Create方法。
另外,登录后基于会话认证,生产环境开发者工具Application标签中看不到session_id和csrf(推测是安全策略导致),但请求失败后查看Network标签,发现X-Csrftoken为undefined,而Cookie中包含csrftoken和session_id。仅生产环境出现该问题,开发环境无异常。
相关代码
后端(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) 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.")
前端代码
let handleSubmit = (e)=>{ e.preventDefault() console.log(Cookies.get('csrftoken')) axios.post('https://somedomain.com/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配置(settings.py)
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','https://somedomain.com/'] SECURE_SSL_REDIRECT = True CSRF_COOKIE_SECURE = True SESSION_COOKIE_SECURE = True SECURE_HSTS_SECONDS = 60 CUSTOM_HEADERS = ( 'Access-Control-Allow-Origin', 'Token', 'User-Type' ) CORS_ALLOW_HEADERS = default_headers + CUSTOM_HEADERS CSRF_COOKIE_SAMESITE = 'none'
解决方案
1. 修复后端PostView认证配置问题
authentication_classes和permission_classes是类属性,不能写在create方法内部,否则认证逻辑不会生效。同时要修正几个细节:
class PostView(viewsets.ModelViewSet): serializer_class = serializer.PostSerializer authentication_classes = [SessionAuthentication] permission_classes = [IsAuthenticated] # 注意是permission_classes,不是permissions_classes def get_queryset(self): queryset = models.Post.objects.all() return queryset @method_decorator(ensure_csrf_cookie) def create(self,request): post = serializer.PostSerializer(data=request.data) if post.is_valid(): # 用validated_data获取验证后的安全数据,而非原始的post.data 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 creation failed.")
2. 统一前后端请求域名
生产环境下前端useEffect中的请求仍使用开发环境域名,导致跨域无法正确获取CSRF Cookie,需替换为生产环境域名:
useEffect(()=>{ const apiDomain = 'https://somedomain.com'; axios.get(`${apiDomain}/posts/`,{withCredentials:true}).then((res)=>{ setPostList(res.data) }) axios.get(`${apiDomain}/csrf/`,{headers:{Authorization:null},withCredentials:true}) },[])
同时确保CSRF_TRUSTED_ORIGINS中生产环境域名末尾无多余斜杠,改为'https://somedomain.com'。
3. 修复CSRF配置冲突
settings.py中重复设置了CSRF_COOKIE_SAMESITE,跨域场景下需统一配置为None(必须配合SECURE属性):
# 删除重复的CSRF_COOKIE_SAMESITE设置,保留以下配置 CSRF_COOKIE_SAMESITE = 'None' CSRF_COOKIE_SECURE = True SESSION_COOKIE_SAMESITE = 'None' # 会话Cookie同步设置,避免跨域会话失效 SESSION_COOKIE_SECURE = True
4. 确保前端正确获取CSRF Token
如果依赖第三方Cookie库存在兼容问题,可改用原生方法提取CSRF Token:
function getCsrfToken() { let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); if (cookie.startsWith('csrftoken=')) { cookieValue = decodeURIComponent(cookie.substring('csrftoken='.length)); break; } } } return cookieValue; } // 在handleSubmit中替换原有写法 axios.post('https://somedomain.com/posts/',post,{ withCredentials:true, headers:{'X-CSRFToken': getCsrfToken()} })
内容的提问来源于stack exchange,提问作者Ghazi
相关产品推荐
相关产品推荐

