Flask默认不防CSRF,必须手动集成令牌机制且三要素缺一不可:服务端生成、模板渲染、提交回传;Flask-WTF中csrf_token为空或校验失败主因是继承错类、模板松散或SECRET_KEY未配置。

Flask 默认不防 CSRF,裸用 request.form 就等于把转账按钮直接摆在攻击者面前——必须手动集成令牌机制,且三要素缺一不可:服务端生成、模板渲染、提交回传。
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>内任意位置(别依赖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 秒,但页面长时间打开或反代超时会提前失效。
立即学习“Python免费学习笔记(深入)”;
- 不要调高过期时间治标,应确保每次新页面加载都刷新
session['csrf_token'],否则重放攻击可绕过 -
generate_csrf()必须用secrets.token_urlsafe(32),禁用random模块——后者不具备密码学安全性 - CSRF Token 生命周期必须和 session 强绑定;若用 Redis 存 session,要确认其可用且未被清空
- Set-Cookie 响应头必须含
HttpOnly=True; Secure=True; SameSite=Lax(开发环境Secure可关,上线必须开)
最容易被忽略的点:token 泄露渠道没堵住,再随机也白搭。别把 csrf_token 塞进 URL 参数、API 响应体或前端控制台日志里——XSS 一触发就全暴露。


















