bigywater.tech
Django · 123

CSRF Trusted Origins 配置

「CSRF verification failed. Origin checking failed」是部署 HTTPS 与前后端分离时最常见的 403。这篇把 CSRF 的来源检查拆开,每一种情形都在 enforce_csrf_checks=True 下请求一遍,并取出 Django 自己记录的失败原因。

同源才放行
Origin 的协议、主机、端口必须与请求一致,否则要在 CSRF_TRUSTED_ORIGINS 里
4_0.E001
CSRF_TRUSTED_ORIGINS 里不带协议的值被 manage.py check 拒绝
代理后面
没配 SECURE_PROXY_SSL_HEADER 时,HTTPS 页面的 POST 会被 403

同源与不同源

实测 · 本机执行输出
  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 时

实测 · 本机执行输出
  没有 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 篇。

分步演示

CSRF 来源检查
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。Client(enforce_csrf_checks=True);令牌先 GET 取得再放进 POST 表单字段;失败原因取自 django.security.csrf 日志
    • 没有验证:AJAX 用 X-CSRFToken 头、CSRF_COOKIE_DOMAIN / SAMESITE 对跨站请求的影响、浏览器实际发出的 Origin 头、带凭据的跨域请求(CORS)
    京ICP备2026067502号-1京公网安备11010502063487号