本文目录导读:

Django 的 CSRF 保护机制在近几个版本中确实变得更严格了,主要体现在默认配置的收紧和潜在攻击面的修复上。
核心变化时间线
- Django 4.0 (2021年底):这是一个重要的分水岭,CSRF 保护的严格性开始显著提升。
- Django 4.2 (2023年初,LTS版本):引入了更深层次的安全防护。
- Django 5.0 (2023年底):继续强化了某些默认行为。
具体严格在哪里?
作为开发者或运维人员,你可能会遇到以下变化:
默认启用 CSRF_COOKIE_SAMESITE (Django 4.0+)
这是最显著的变化,Django 4.0 及更高版本将 CSRF_COOKIE_SAMESITE 的默认值从空字符串()改为了 'Lax'。
- 旧行为: (无设置,浏览器行为不确定,可能导致 CSRF cookie 在跨站请求中被发送,增加了风险)
- 新行为:
'Lax'(默认设置,浏览器只会在顶级导航时发送 CSRF cookie,POST 等表单请求通常不会自动发送,从而阻止了大多数跨站请求伪造攻击)
这个改动直接导致了许多未正确配置的第三方 POST 请求(从 App 或其它站点直接向你的 Django 后端提交表单)会失败,因为 CSRF cookie 不会随请求发送,CSRF 验证无法通过。
CSRF_TRUSTED_ORIGINS 的验证更严格 (Django 4.1+)
-
旧行为:
CSRF_TRUSTED_ORIGINS只验证Host头部是否匹配。 -
新行为:Django 会同时验证
Referer和Origin头部,并且要求CSRF_TRUSTED_ORIGINS中配置的地址必须包含协议前缀(https://example.com而不是example.com)。- 影响:如果你在
settings.py里只写着['example.com'],而没有写['https://example.com'],所有 HTTPS 请求的 CSRF 验证都会失败,并返回 403 错误,很多来自空白Referer或Origin的请求(如本地测试、某些老旧浏览器或代理)也会被拒绝。
- 影响:如果你在
CSRF_FAILURE_VIEW 默认行为更安全 (Django 4.2+)
- 旧行为:CSRF 验证失败时,默认抛出一个 403 错误页面。
- 新行为:默认的
CSRF_FAILURE_VIEW(django.views.csrf.csrf_failure) 在返回 403 错误页面时,会 不再渲染任何用户输入到页面内容中,防止反射型 XSS 攻击,这在 CSRF token 被篡改或缺失时,避免了错误信息泄露敏感数据。
CSRF_COOKIE_HTTPONLY 的默认选项 (Django 5.0+)
-
旧行为:
CSRF_COOKIE_HTTPONLY默认为False(可以通过 JavaScript 读取 CSRF token,方便前后端分离) -
新行为:Django 5.0 并没有直接修改默认值,但在文档和官方指南中强烈建议:除非你必须通过 JavaScript 读取 CSRF token,否则应将其设置为
True,这意味着 CSRF cookie 对 JavaScript 不可见,彻底防止了通过 XSS 漏洞窃取 CSRF token 的路径。- 提示:如果你的前端通过
getCookie('csrftoken')来获取 token,记得将其改为通过 Django 模板渲染到页面中(<meta name="csrf-token" content="{{ csrf_token }}">)。
- 提示:如果你的前端通过
这会带来哪些“痛苦”?
作为开发者,你可能遇到以下常见问题:
- 第三方回调失败:微信支付、支付宝回调、OAuth2 回调等从外部 POST 数据到 Django,因
SameSite=Lax导致 CSRF 失败,你需要将相关域名添加到CSRF_TRUSTED_ORIGINS。 - 本地开发/测试失败:使用 Postman、curl 或测试脚本时,忘记携带或正确设置 CSRF cookie 和
X-CSRFToken头部,直接返回 403。 - 反向代理/负载均衡问题:
Referer或Origin头部被代理剥离,Django 无法验证来源,导致失败,你需要确保代理不丢弃这些头部,或配置SECURE_PROXY_SSL_HEADER等。 - 前后端分离项目:如果前端域名与后端域名不同(跨域),
SameSite=Lax或Strict会阻止 CSRF cookie 发送,这时候通常需要改为使用SameSite=None; Secure(但需 HTTPS),或者放弃 cookie 方式,改用Token认证(如 JWT)。
总结与建议
| 特性 | 旧的宽松行为 | 新的严格行为 | 影响场景 |
|---|---|---|---|
| CSRF_COOKIE_SAMESITE | 默认空字符串 (容易泄露) | 默认 Lax (大部分 POST 不允许) |
第三方 POST 回调、跨域请求 |
| CSRF_TRUSTED_ORIGINS | 只验证 Host | 验证 Referer/Origin,且需要协议 | 代理、反向代理、本地测试 |
| CSRF_FAILURE_VIEW | 可能渲染用户输入 | 默认不渲染任何输入 | 安全防御,XSS 保护 |
| CSRF_COOKIE_HTTPONLY | 默认 False (JS可读) | 推荐 True (JS不可读) | 前后端分离,XSS防御 |
给你的代码检查清单:
- 检查
settings.py中的CSRF_TRUSTED_ORIGINS,确保每个域名都带有http://或https://前缀。 - 如果你的应用需要完全跨域(例如前端
app.example.com,后端api.example.com),考虑:- 配置
CSRF_COOKIE_SAMESITE = 'None'和CSRF_COOKIE_SECURE = True(要求 HTTPS)。 - 或者放弃 CSRF 保护,改用 JWT Token 或 OAuth Bearer Token 框架,因为 CSRF 本质上是针对 cookie 认证的防护。
- 配置
- 如果你依赖 JS 获取 csrftoken,请将其改为从 HTML 模板中渲染出来(或者用
getCookie但注意SameSite和HttpOnly限制)。 - 升级到 Django 5.x 后,运行完整的回归测试,特别是涉及跨站 POST、API 回调、文件上传等场景。
一句话总结:Django CSRF 保护确实更严格了,默认行为开始主动阻止所有不被信任的跨站请求,如果不能适应,可以调整配置(如 CSRF_TRUSTED_ORIGINS、SameSite),但不建议关闭它。