Django 4.2 中 csrf_token 模板标签报 403,主因是 CSRF_USE_SESSIONS=True 下 token 与 session 强绑定,会话过期、中间件顺序错(SessionMiddleware 必须在 CsrfViewMiddleware 前)、多标签页登出或 CSRF/SESSION Cookie 过期时间不一致均导致校验失败。

CSRF令牌失效在 Django 4.2 中通常不是“随机发生”,而是由明确的会话/缓存/请求链路问题触发;直接修 CSRF_FAILURE_VIEW 或关掉防护只会掩盖真实缺陷,反而放大安全风险。
为什么 csrf_token 模板标签突然报 403?
Django 4.2 默认使用基于会话的 CSRF 保护(CSRF_USE_SESSIONS=True),这意味着:csrf_token 的值从 request.session 读取,而非 cookie。一旦会话过期、被清空、或中间件顺序错乱,模板里渲染的 token 就和后端校验时拿到的不一致。
常见诱因包括:
- 用户长时间未操作,
SESSION_COOKIE_AGE到期但页面仍开着,提交时 token 已失效 -
django.contrib.sessions.middleware.SessionMiddleware被放在django.middleware.csrf.CsrfViewMiddleware之后(Django 4.2 要求前者必须在后者之前) - 使用了自定义中间件(如 JWT 鉴权中间件)提前终止了 request 流程,导致 session 未加载
- 多标签页中一个标签登出(清 session),其他标签再提交就必然失败
如何让表单提交前主动检测 CSRF 状态?
不能依赖前端 JS 自行生成 token(违反 Django CSRF 设计),但可以加一层轻量级探测:用 fetch 请求一个只返回当前 CSRF token 的视图,在表单 submit 事件里先检查它是否有效。
示例视图:
from django.http import JsonResponse
from django.views.decorators.csrf import ensure_csrf_cookie
<p>@ensure_csrf_cookie
def csrf_token_endpoint(request):
return JsonResponse({'ok': True})
</p>
前端调用逻辑(放在 form 的 onsubmit 或按钮点击里):
async function canSubmit() {
try {
const res = await fetch('/api/csrf/');
return res.status === 200;
} catch (e) {
return false;
}
}
注意:@ensure_csrf_cookie 是关键——它强制 Django 在响应头写入 Set-Cookie: csrftoken=xxx,且该 cookie 不受 CSRF_USE_SESSIONS=True 影响,可作为 fallback 校验依据。
CSRF_COOKIE_AGE 和 SESSION_COOKIE_AGE 必须对齐
如果 CSRF_COOKIE_AGE(默认 31449600 秒 ≈ 1 年)远大于 SESSION_COOKIE_AGE(默认 1209600 秒 ≈ 2 周),用户 session 过期后,浏览器仍带着旧的 csrftoken cookie 发请求,而 Django 后端因找不到对应 session,无法验证该 token,直接 403。
建议统一设为相同值(尤其在生产环境):
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 # 7 天 CSRF_COOKIE_AGE = SESSION_COOKIE_AGE
另外,确保 CSRF_COOKIE_HTTPONLY=False(默认就是 False),否则 JS 无法读取 cookie 做前端 fallback 校验。
AJAX 请求里漏传 X-CSRFToken 头是高频错误点
Django 4.2 对 AJAX 的 CSRF 校验更严格:即使你用了 @csrf_exempt,只要视图没显式禁用(比如加 @csrf_protect 或中间件生效),且请求头不含 X-CSRFToken,就会 403。
正确做法不是绕过,而是自动注入:
- 从
document.cookie解析csrftoken值(注意:仅当CSRF_USE_SESSIONS=False时 cookie 才有效) - 或更稳妥地:在页面初始化时用
fetch('/api/csrf/')获取一次 token,缓存在 JS 变量里,后续所有 AJAX 请求都带上 - jQuery 用户注意:
$.ajaxSetup({xhrFields: {withCredentials: true}})不解决 CSRF,必须手动加 header
示例 header 设置:
fetch('/api/submit/', {
method: 'POST',
headers: {
'X-CSRFToken': getCsrfToken(), // 你封装的获取函数
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
最易被忽略的是:CSRF 校验失败时,Django 4.2 默认返回的 HTML 错误页不包含任何调试线索,连日志都不打(除非开启 DEBUG=True)。上线前务必确认 LOGGING 配置里有 django.security.csrf 的 ERROR 级别捕获,否则你根本不知道 token 是在哪一步、被谁清掉的。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











