flask默认不防csrf,必须手动集成令牌机制;flask-wtf需正确继承flaskform、配置secret_key、显式渲染{{ form.csrf_token }},ajax提交需手动传token并用multidict构造表单,token须与session强绑定且避免泄露。

Flask 默认完全不防 CSRF,裸用 request.form 或 request.get_json() 就等于把转账按钮直接暴露给攻击者——必须手动集成令牌机制,且服务端生成、模板渲染、提交回传三者缺一不可。
Flask-WTF 表单里 csrf_token 为空或校验失败
根本原因通常是继承错类、模板写法松散,或 SECRET_KEY 没配。WTForms 的 CSRF 保护只对 FlaskForm 生效,不是 Form。
- 确认导入的是
from flask_wtf import FlaskForm,不是from wtforms import Form -
form.hidden_tag()会同时渲染csrf_token和时间戳字段;手写<input name="csrf_token" value="{{ csrf_token() }}">缺少时间戳,签名验证必失败 - 最稳写法是显式写
{{ form.csrf_token }},放在<form></form>内任意位置(别依赖hidden_tag()) - 检查
app.config['SECRET_KEY']是否已设——未设置时 token 生成静默失败,form.csrf_token渲染为空字符串
纯 HTML 表单或 AJAX 提交后返回 400 错误
浏览器不会自动把隐藏字段塞进 JSON 请求体,application/json 类型请求默认收不到 token,这是 400 最常见源头。
- 纯 HTML 表单需手动加:
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">,前提是后端已将csrf_token()函数传入模板上下文(或通过CSRFProtect自动注入) - AJAX 发
application/json请求时,必须手动提取:document.querySelector('input[name="csrf_token"]').value,不能从 URL 或响应体里捞 - 把 token 加进 payload:
{"username": "a", "csrf_token": "abc123"};后端不能只调LoginForm(request.form),得先request.get_json(),再用MultiDict构造表单:form = LoginForm(MultiDict(request.get_json())) - 若用
fetch或axios,且希望走标准校验路径,可改发application/x-www-form-urlencoded,或在请求头带X-CSRFToken并配置WTF_CSRF_HEADERS = ['X-CSRFToken']
token 过期快、多标签页切换后提交就失败
不是 token 随机性不够,而是签名里的时间戳校验太严,默认 3600 秒,但页面长时间打开或反代超时会提前失效。
- 不要调高过期时间治标,应确保每次新页面加载都刷新
session['csrf_token'],否则重放攻击可绕过 -
generate_csrf()必须用secrets.token_urlsafe(32),禁用random模块——后者不具备密码学安全性 - CSRF Token 生命周期必须和 session 强绑定;
Set-Cookie响应头必须带HttpOnly=True;Secure=True;SameSite=Lax(开发环境Secure可关,上线必须开) - 绝不能把
csrf_token塞进 URL 参数(如?token=xxx),会被代理、日志、Referer 泄露;API 返回 JSON 时,别把csrf_token明文塞进响应体
真正容易被忽略的点是:token 必须随每次页面加载刷新,而不是复用旧 session 值;一旦 token 泄露渠道没堵住(比如 XSS、日志打印、前端缓存),再强的随机性也白搭。











