flask/werkzeug 默认 csrfprotect 不适合前后端分离,因其依赖服务端 session 存储 token,而无状态 api 下 session 常未建立或被跨域拦截,导致 validate_csrf() 直接抛 invalidcsrftoken。

为什么 Flask/Werkzeug 默认的 CSRFProtect 不适合前后端分离场景
因为 CSRFProtect 依赖服务端 session 存储 token,而前后端分离时前端常走无状态 API(比如 Vue/React 调用 /api/login),session 可能未正确建立或被跨域拦截。此时 validate_csrf() 直接抛 InvalidCSRFToken,不是前端漏传,是后端压根没存上 token。
Double submit cookie 方案绕过服务端存储——token 同时写入 cookie 和请求头(或 body),服务端只比对二者是否一致,不查 session。
实操建议:
- 后端生成随机 token(如
secrets.token_urlsafe(32)),设为HttpOnly=False的 cookie(否则 JS 拿不到) - 前端在每次敏感请求(
POST/PUT/DELETE)中,从document.cookie提取该 token,放入X-CSRF-Token请求头 - 后端中间件读取 cookie 中的 token 和请求头中的 token,字符串严格相等才放行
如何手动实现 double submit 校验(不用 Flask-WTF)
Flask-WTF 的 CSRFProtect 不支持纯 cookie+header 校验模式,得自己写轻量中间件。核心就三步:生成、下发、比对。
立即学习“Python免费学习笔记(深入)”;
示例代码逻辑(非完整装饰器,仅关键判断):
from flask import request, make_response, jsonify
import secrets
<p>def set_csrf_cookie():
token = secrets.token_urlsafe(32)
resp = make_response(jsonify({'status': 'ok'}))
resp.set_cookie('XSRF-TOKEN', token, httponly=False, samesite='Lax')
return resp</p><p>def validate_double_submit():
cookie_token = request.cookies.get('XSRF-TOKEN')
header_token = request.headers.get('X-CSRF-Token')
if not (cookie_token and header_token):
return False</p><h1>注意:必须恒定时间比较,防时序攻击</h1><pre class='brush:python;toolbar:false;'>return secrets.compare_digest(cookie_token, header_token)常见错误现象:
- cookie 设了
HttpOnly=True→ 前端 JS 读不到,X-CSRF-Token头永远为空 - 用普通
==比较 token → 可能被时序攻击利用 - 没处理
SameSite策略 → 跨站请求时 cookie 不携带,校验失败
Vue/React 前端怎么安全读取并发送 CSRF token
不能直接 document.cookie.match(/XSRF-TOKEN=([^;]+)/) 手动解析,容易出错;现代框架有更稳的方式。
使用场景:
- 登录成功后,后端返回带
XSRF-TOKENcookie 的响应,前端无需主动取值 - 后续请求统一用 axios 拦截器自动加 header:
axios.defaults.headers.common['X-CSRF-Token'] = token - token 必须从 cookie 中实时读,不能缓存到内存变量里——否则刷新页面后失效
注意点:
- axios 默认不带 cookie,需设
withCredentials: true,否则浏览器不发 cookie - 如果后端 cookie 设了
Secure=True,但前端用 http 访问,cookie 不会发送 → 开发环境要关掉Secure - fetch API 需显式写
credentials: 'include',否则同理丢 cookie
和 JWT 共存时 CSRF 防护会不会冲突
不会冲突,但容易误以为“用了 JWT 就不用 CSRF 防护”——这是典型误区。JWT 解决的是身份认证,不是 CSRF。
原因:
- CSRF 攻击本质是“用户已登录 + 浏览器自动带凭证(cookie 或 localStorage 内的 token)”,攻击者诱导用户发起请求,浏览器照发
- 如果 JWT 存在 localStorage,且前端用它拼在
Authorization: Bearer xxx里,那确实不走 cookie,CSRF 风险低(但 XSS 风险高) - 但如果 JWT 也存在 cookie(尤其
HttpOnly=False),那它和 CSRF token 一样会被自动带上,double submit 仍需独立校验
性能与兼容性影响:
- 每次请求多一次字符串比较,开销可忽略(
secrets.compare_digest是 C 实现) - IE11 不支持
SameSite=None,若需兼容,cookie 的SameSite值设成Lax更稳妥
真正容易被忽略的点是:cookie 的 Domain 和前端请求的 Origin 必须匹配,否则浏览器拒绝发送。开发时 localhost:3000 调 localhost:5000,cookie 的 domain 若设成 example.com,就直接失效。调试时先 curl 看 response headers 里的 Set-Cookie 字段是否符合预期。










