「CSRF verification failed. Origin checking failed」是部署 HTTPS 与前后端分离时最常见的 403。这篇把 CSRF 的来源检查拆开,每一种情形都在 enforce_csrf_checks=True 下请求一遍,并取出 Django 自己记录的失败原因。
Origin 与 Host 同源 (https://app.example.com) → 200 Origin: https://evil.example → 403 Origin checking failed - https://evil.example does not match any trusted origins. Origin 同源但缺少令牌 → 403 CSRF token missing. Origin: http://app.example.com(协议不同) → 403 Origin checking failed - http://app.example.com does not match any trusted origins. Origin: https://app.example.com:8443(端口不同) → 403 Origin checking failed - https://app.example.com:8443 does not match any trusted origins.
失败原因来自 django.security.csrf 日志的消息,和 403 页面上展示的 reason 是同一个。
[CSRF_TRUSTED_ORIGINS = []] Origin: https://spa.example.org → 403 Origin checking failed - https://spa.example.org does not match any trusted origins. [CSRF_TRUSTED_ORIGINS = ['https://spa.example.org']] Origin: https://spa.example.org → 200 Origin: https://other.example.org → 403 Origin checking failed - https://other.example.org does not match any trusted origins.
[CSRF_TRUSTED_ORIGINS = ['https://*.example.org']] Origin: https://a.example.org → 200 Origin: https://a.b.example.org → 200 Origin: https://example.org → 200 Origin: http://a.example.org → 403 Origin checking failed - http://a.example.org does not match any trusted origins. 写成不带协议的 'spa.example.org' 时 manage.py check: ?: (4_0.E001) As of Django 4.0, the values in the CSRF_TRUSTED_ORIGINS setting must start with a scheme (usually http:// or https://) but found spa.ex
通配符也匹配没有子域名的 https://example.org,和 ALLOWED_HOSTS 的「.」写法一样宽松,用之前要想清楚。
没有 Origin、没有 Referer → 403 Referer checking failed - no Referer. Referer: https://app.example.com/form/ → 200 Referer: https://evil.example/x → 403 Referer checking failed - https://evil.example/x does not match any trusted origins.
[没有设 SECURE_PROXY_SSL_HEADER]
Django 看到 http,浏览器 Origin: https://app.example.com → 403 Origin checking failed - https://app.example.com does not match any trusted origins.
[SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')]
代理加了 X-Forwarded-Proto: https → 200这里是用测试客户端加请求头模拟的代理,没有真的起 nginx。SECURE_PROXY_SSL_HEADER 的风险(头可能被伪造)见第 127 篇。