CSRF Token必须每次请求更换,因其重用等同于泄露门禁卡;攻击者截获后可无限伪造请求,即使session已过期。Django、Flask-WTF、FastAPI等框架默认每次生成新token,手动缓存或硬编码即失效防护。

CSRF Token 为什么必须每次请求都换?
因为重用 token 等同于把门禁卡借给别人用。攻击者一旦截获一次合法 csrf_token,就能反复伪造请求——哪怕用户已登出、session 过期,只要 token 没失效,攻击就有效。
Django 默认的 get_token()、Flask-WTF 的 csrf_token 字段、FastAPI + Starlette 的 CSRFProtect 中间件,都默认在每次 session 创建或调用时生成新值。但注意:如果你手动缓存了 token(比如存在全局变量或 Redis 中长期复用),就等于主动关掉防护。
- 不要把
csrf_token存进数据库或配置文件里长期复用 - 不要在 AJAX 请求头里硬编码一个固定字符串当
X-CSRFToken - 如果用了前后端分离(如 Vue + FastAPI),token 必须从后端接口动态获取,且每次提交前重新 fetch 一次
表单里怎么塞 token 才不被绕过?
CSRF 防护只对“状态变更请求”(POST/PUT/DELETE)有意义,而 token 必须出现在服务端可验证的位置——不能只靠前端 JS 插入,也不能只放 header 里漏掉表单字段。
Django 模板中用 {% csrf_token %} 是安全的,因为它会自动绑定当前 session;Flask-WTF 的 {{ form.hidden_tag() }} 同理。但如果你手写 HTML 表单,又忘了加 <input type="hidden" name="csrf_token" value="{{ csrf_token }}">,那整个验证就形同虚设。
立即学习“Python免费学习笔记(深入)”;
- GET 请求不用传 token,但别在 GET 里做删改操作(比如
/api/user/delete?id=123) - AJAX 提交时,既要设置
headers: {'X-CSRFToken': token},也要确保后端同时检查 header 和 body(有些框架只查 header) - 如果用了 CDN 或反向代理,确认它没过滤或缓存带
Set-Cookie的响应——否则用户拿不到新 cookie,token 就对不上
为什么校验失败总报 403 Forbidden 却没提示?
这不是 bug,是设计使然。暴露具体失败原因(比如 “token 过期” 或 “token 不匹配”)会帮攻击者做差分测试,所以主流框架默认只返回 403,连日志都不打细节。
调试时可以临时开启 debug 模式:Django 设置 DEBUG = True 并捕获 django.middleware.csrf.RejectRequest 异常;Flask-WTF 在 app.config['WTF_CSRF_CHECK_DEFAULT'] = True 下配合 try/except ValidationError;FastAPI 则需在 CSRFProtect 初始化时传 on_error 回调。
- 生产环境别改错误响应体,但可以在 Nginx 日志里加
$sent_http_x_csrf_protection这类自定义 header 辅助排查 - 注意浏览器同站策略(SameSite)影响:如果 cookie 设了
SameSite=Lax,跨站 POST 时 cookie 不会带上,导致后端找不到 session,自然校验失败 - 移动端 WebView 加载 H5 页面时,部分安卓系统会清空第三方 cookie,造成 token 和 session 不一致
CSRF Token 和 JWT 能混着用吗?
能,但别把两者职责搞混。JWT 是认证凭证,解决“你是谁”;CSRF Token 是防重放令牌,解决“这个请求真是你发起的吗”。把 JWT 放在 Authorization header 里,再额外在 body 或 header 里传一个独立的 csrf_token,才是正确姿势。
常见错误是直接拿 JWT 的 payload 做 CSRF 校验——比如取 exp 时间戳当 token,或者用 user_id + salt 签名生成。这会导致 token 可预测、可重放,完全失去 CSRF 防护意义。
- CSRF Token 必须由服务端生成、绑定 session 或一次性随机数(如
secrets.token_urlsafe(32)),不能由客户端构造 - 如果用了无状态 JWT,那就得搭配服务端存储的 token 白名单(比如 Redis 里存
jwt_jti:csrf_token映射),否则无法注销或提前失效 - 别为了省事把
csrf_token塞进 JWT 的 custom claim 里传回来——它会被缓存、转发、甚至被中间人解码
最麻烦的地方往往不在生成和校验逻辑本身,而在于 cookie 的 Secure、HttpOnly、SameSite 三个属性怎么配,以及前端是否真按预期发送了 cookie 和 header。这些细节一错,token 就对不上,而且很难从错误信息里看出根源。










