csrf令牌无效本质是防护链条断裂,需服务端生成、会话绑定、前端嵌入、请求携带、后端校验五环节协同;硬性前提是启用中间件、配置有效密钥、用户已建立有效会话。

CSRF令牌无效,本质不是“没开防护”,而是防护链条中某个环节断开了。Form Protection(表单防护)真正起效,靠的是一整套协同动作:服务端生成、会话绑定、前端嵌入、请求携带、后端校验——缺一不可。
Form Protection必须开启的三个硬性前提
- 服务端已启用CSRF中间件(如Django的
CsrfViewMiddleware、Flask的CSRFProtect),且在中间件链中位置正确(Session之后、Auth之前) - 应用已配置有效且稳定的密钥(
SECRET_KEY或app.secret_key),未在运行中变更或为空 - 用户已建立有效会话(即登录成功、session已写入且未过期),因为CSRF令牌必须与会话强绑定
表单里怎么嵌入才真正生效
- Django模板中必须使用
{% csrf_token %},且放在<form></form>标签内部、任何<input type="submit">之前;写成{{ csrf_token }}或手写<input name="csrfmiddlewaretoken" value="...">都不完整,前者不设Cookie,后者缺签名和时间戳 - Flask + WTForms 必须继承
FlaskForm,模板中调用{{ form.hidden_tag() }};若手动拼<input name="csrf_token">,会跳过时间戳校验,导致重放攻击可绕过 - Laravel用
@csrf或<meta name="csrf-token" content="{{ csrf_token() }}">,AJAX请求需每次动态读取该meta值,不能靠全局$.ajaxSetup缓存一次
为什么填完表单一提交就报错
- 令牌随页面加载生成,但用户停留过久(超默认1小时)、切换标签页、或服务端因错误(如验证失败)刷新了会话——旧令牌即失效
- 前端JS用
fetch或axios发请求时,没把令牌放进X-CSRFToken头(Django/Flask/Laravel均需),或没同步更新Cookie中的csrftoken字段 - 反向代理、CDN或浏览器插件拦截了
Set-Cookie响应头,导致客户端始终拿不到或无法保存csrftokenCookie
验证环节最容易被忽略的细节
- 后端比对必须用恒定时间函数(如PHP的
hash_equals()、Python的secrets.compare_digest()),防止时序攻击 - 令牌不能仅比对字符串,还要校验签名和时间戳(WTForms默认含3600秒时效,Django默认无时间限制但依赖session有效期)
- 若用API模式,且认证方式是JWT或Bearer Token(非Cookie),则CSRF防护可豁免;但只要用了
session存用户身份,所有状态变更请求(POST/PUT/DELETE)都必须校验
不复杂但容易忽略











