csrf token必须由服务端动态生成并注入,不可硬编码;正确做法是每次get渲染页面时生成唯一token写入cookie(httponly: false, samesite: 'lax', secure: false),前端读取后通过x-csrftoken头提交,服务端严格比对header与cookie值。

CSRF Token 必须从服务端动态注入,不能硬编码
本地测试时最容易犯的错,是把 csrf_token 写死在前端 HTML 或 JS 里,比如直接写 headers: {'X-CSRFToken': 'abc123...'}。这会导致所有请求共用同一个 token,服务端一验证就失效——因为每次 GET 渲染页面时,服务端应生成并设置新的 csrf_token cookie,且该值必须唯一绑定当前会话。
正确做法是:服务端在返回转账页(如 /transfer GET)时,调用 res.cookie('csrf_token', getRandomString(48)),同时确保前端 JS 通过 document.cookie 或 fetch 响应头读取它。不要依赖 localStorage 存储或手动拼接字符串。
-
getRandomString(48)必须每次调用都生成新值,不能缓存或复用 - cookie 设置需带
httpOnly: false(否则 JS 无法读取),但secure: false可接受(本地 http 环境) - 若用 Express +
cookie-parser,务必在app.use(cookieParser())后注册路由,否则req.cookies为空
POST 请求必须携带 X-CSRFToken header,且校验逻辑不能跳过
VSCode 调试时常见现象:前端发 POST 成功,但服务端没做任何校验,或只校验了 header 却忽略 cookie 对比。真正的防护逻辑是两步缺一不可:读取 req.headers['x-csrftoken'],再读取 req.cookies.csrf_token,二者严格相等才放行。
示例错误写法:if (req.headers['x-csrftoken']) { /* 直接执行转账 */ } —— 这等于没防,攻击者可随意伪造 header。
- 校验必须区分大小写:Express 默认转为小写,
req.headers['x-csrftoken']是标准写法,X-CSRFToken会被 normalize 成小写 - 若 token 不匹配,应立即
return res.status(403).send('Invalid CSRF token'),不能继续执行业务逻辑 - 注意:GET 请求无需校验,但所有状态变更的 POST/PUT/DELETE 都必须校验
Referer 校验仅作辅助,不能替代 Token
有人图省事,在 VSCode 本地跑个 app.post('/transfer', (req, res) => { if (req.headers.referer?.startsWith('http://localhost:3000')) { ... } }) 就以为防住了。这是危险的——Referer 可被浏览器插件、代理或某些客户端主动删除,且本地开发时跨端口(如前端 5173、后端 3000)会导致 Referer 值为 http://localhost:5173,不匹配预设值。
Referer 检查只能作为第二道防线,且必须配合白名单而非简单 startsWith:
- 生产环境白名单应精确到协议+域名+端口,例如
https://mybank.com,不能只写mybank.com - 本地测试时,若前后端不同端口,Referer 值会是
http://localhost:5173,需显式加入白名单数组 - 若启用
SameSite=Laxcookie,Referer 校验可弱化,但 Token 仍是主防手段
VSCode 调试时容易漏掉 Cookie 的 SameSite 和 Secure 属性
本地启动 Node 服务(http://localhost:3000)时,若 cookie 缺少 sameSite: 'lax' 或错误设置了 secure: true,会导致浏览器拒绝发送 cookie,进而使 req.cookies.csrf_token 为空,所有 POST 校验失败——你看到的是“token 无效”,实际是 cookie 根本没传过来。
调试建议:打开 Chrome DevTools → Application → Cookies,确认 csrf_token 是否存在、SameSite 值是否为 Lax(非 Strict,否则表单提交可能被拦截)、Secure 是否为 false(本地 http 环境必须关掉)。
- Express 中设置 cookie 正确写法:
res.cookie('csrf_token', token, { httpOnly: false, sameSite: 'lax', secure: false }) -
sameSite: 'none'必须搭配secure: true,否则浏览器拒绝设置,本地 http 下禁用 - 若用 Vite/HMR 开发前端,注意其 dev server 默认跨域,需在
vite.config.ts中配server.proxy避免 Referer 变成 proxy 地址
真实防御的关键不在 token 生成多复杂,而在于每次交互都强制双向验证:服务端给的 cookie 值,必须和服务端收到的 header 值完全一致,且这个 pair 只对当前会话有效。本地测试时,任何绕过、缓存、硬编码或属性遗漏,都会让整个防护形同虚设。











