Django跨域CSRF验证持续失败问题排查求助
前后端分离架构下,前端POST请求已在Cookie头携带csrftoken,单独放入请求头也无效,CSRF验证始终失败。更奇怪的是Django的request.META和request.headers里找不到该令牌信息。当前用Nginx做反向代理,已启用TLS加密。
核心困惑:前端已传递X_CSRFTOKEN和Cookie头,但后端检测不到。
注:移除
http://dev.admin.lt配置项时,会出现如下错误:(Origin checking failed - http://dev.admin.lt does not match any trusted origins.): /auth/login/
Django & CORS配置(中间件顺序已正确配置)
CORS_ALLOWED_ORIGINS = [ "https://127.0.0.1", "https://admin.lt", "https://dev.admin.lt", "https://api.admin.lt", "http://dev.admin.lt", # NOTE, without this we get another error ] CSRF_TRUSTED_ORIGINS = [ "https://127.0.0.1", "https://admin.lt", "https://dev.admin.lt", "https://api.admin.lt", "http://dev.admin.lt" # NOTE, without this we get another error ] CORS_ALLOW_CREDENTIALS = True CSRF_COOKIE_SECURE = True CSRF_COOKIE_HTTPONLY = True CSRF_COOKIE_SAMESITE = None CSRF_COOKIE_DOMAIN = ".admin.lt" # Is this actually doing anything? SESSION_COOKIE_SECURE = True SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_SAMESITE = None SESSION_COOKIE_DOMAIN = ".admin.lt"
后端捕获的请求头信息
ipdb> request.headers {'Content-Length': '', 'Content-Type': 'text/plain', 'Connection': 'upgrade', 'Host': 'api.domain.lt', 'X-Forwarded-For': '123.123.123.202', 'Cookie': 'csrftoken=caOdoVlJeaAxUzXLtQvjhgEXunWX5K5V; sessionid=jajl1faqtfkmsk9q8ykbjge6obk14wwh', 'Origin': 'http://dev.domain.lt', 'Accept': '*/*', 'Accept-Language': '*', 'Sec-Fetch-Mode': 'cors', 'User-Agent': 'node', 'Accept-Encoding': 'br, gzip, deflate'}
Nginx反向代理配置
server { server_name api.domain.lt; client_max_body_size 500m; #auth_basic "Restricted Content"; #auth_basic_user_file /etc/nginx/.htpasswd; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; proxy_set_header X-Forwarded-For $remote_addr; location / { proxy_pass http://localhost:8000; } listen 443 ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live/api.domain.lt/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/api.domain.lt/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot }
排查方向建议
域名不一致问题:请求头里的Origin是
http://dev.domain.lt,但配置里的信任源是dev.admin.lt,二者完全不匹配。先统一域名(比如把配置里的dev.admin.lt改成dev.domain.lt),CSRF验证会严格校验Origin与信任源的一致性。Secure Cookie与HTTP Origin冲突:设置了
CSRF_COOKIE_SECURE = True意味着Cookie仅通过HTTPS传输,但前端Origin是HTTP的http://dev.domain.lt,浏览器会拒绝在HTTP环境下发送Secure Cookie。要么把前端切换到HTTPS,要么临时将CSRF_COOKIE_SECURE设为False测试(生产环境不建议)。Nginx代理头缺失:Django需要识别请求来自HTTPS,否则会认为请求是HTTP,导致Secure Cookie不生效。在Nginx的
proxy_set_header中添加:proxy_set_header X-Forwarded-Proto $scheme;同时在Django配置中添加:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') USE_X_FORWARDED_HOST = TrueCSRF_COOKIE_HTTPONLY的影响:
CSRF_COOKIE_HTTPONLY = True会禁止前端JS读取Cookie中的csrftoken,这会导致前端手动设置X_CSRFTOKEN头时可能拿到旧值或空值。可以临时将CSRF_COOKIE_HTTPONLY设为False,测试前端是否能正确获取并传递令牌。Cookie域名匹配问题:
CSRF_COOKIE_DOMAIN = ".admin.lt",但请求的API域名是api.domain.lt,前端Origin是dev.domain.lt,三个域名主域不一致(admin.lt vs domain.lt),导致Cookie无法跨域传递。需要将Cookie域名统一为.domain.lt,或确保前后端API在同一主域下。
内容的提问来源于stack exchange,提问作者Džiugas Bižokas

