csrf token 防御需严丝合缝:服务端须绑定 session 动态生成唯一 token,前端须从 dom 安全提取并手动携带至请求头或表单,且所有状态变更接口(含 api、上传、graphql)均须校验,否则将触发 403 或失效。

CSRF Token 不是加个 {% csrf_token %} 就能防住的——它必须和服务端 session 生命周期、传输路径、前端提取方式三者严丝合缝,否则你看到的只会是反复出现的 403 Forbidden 或 Invalid CSRF token。
服务端生成的 Token 必须绑定当前 session
Token 值不能硬编码、不能复用、不能从 URL 或 localStorage 读取。浏览器里看到的 csrf_token 值,必须由服务端在渲染 HTML 前实时生成,并写入当前用户 session(或加密后存入 Cookie,但需设 SameSite=Lax)。
- Django 默认将 Token 存 session;若改用
CSRF_USE_SESSIONS = False,则 Token 存 Cookie,此时响应头必须含Set-Cookie: csrftoken=...; SameSite=Lax - Flask +
flask-wtf中,{{ form.hidden_tag() }}自动注入 Token,但前提是请求已触发 session 初始化(比如用户已登录或访问过带 session 的路由) - Express 使用
csrf-protection时,中间件必须挂载在body-parser之后、路由之前,否则req.body为空导致校验失败
前端必须从 DOM 安全提取,不能硬编码或跨域传
Token 不会自动进 AJAX 请求体或 header,前端必须手动提取并携带;且不能把值写死在 JS 里,也不能从第三方来源(如 URL 参数、localStorage)获取——XSS 漏洞下这些地方极易被读取。
- 推荐方式:在 HTML 中用
<meta name="csrf-token" content="{{ csrf_token }}">,JS 里用document.querySelector('meta[name="csrf-token"]')?.getAttribute('content')获取 - 表单提交可直接用 hidden 字段:
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">,但仅适用于传统form.submit() -
fetch发送POST/PUT/DELETE时,必须显式设置 header:headers: { 'X-CSRF-Token': token };若服务端没配置从 header 读取(如 Django 需设CSRF_HEADER_NAME = 'HTTP_X_CSRF_TOKEN'),照样 403
多标签页、页面刷新、缓存是高频翻车点
看似是前端漏传,实际八成是服务端生命周期处理出错。
- 多标签页同时打开同一页面 → 若服务端启用“一次性 Token”(如某些 PHP 实现),第二个标签提交时 Token 已被消耗,直接报错;应改用“每会话单 Token”模式
- 页面后退再提交 → 浏览器可能缓存旧 HTML(含过期 Token),需在服务端响应加
Cache-Control: no-store, no-cache - AJAX 提交前未检查 Token 是否仍有效(例如用户长时间未操作导致 session 过期),应配合 403 响应做静默刷新或跳登录
最常被忽略的是:Token 校验必须发生在所有敏感操作入口,包括 API 路由、文件上传接口、甚至 GraphQL mutation resolver —— 只要涉及状态变更,就必须走校验链。漏掉一个,就等于整套防护形同虚设。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











