Flask CSRF防护必须手动集成三要素:服务端生成、模板渲染、提交回传;Flask-WTF需显式写{{ form.csrf_token }},纯HTML表单须手动加隐藏字段,AJAX JSON请求需前端提取并携带token,后端用MultiDict解析,token须与session强绑定且用secrets生成。

Flask 默认不防 CSRF,裸用 request.form 就等于把转账按钮直接摆在攻击者面前——必须手动集成令牌机制,且三要素缺一不可:服务端生成、模板渲染、提交回传。
Flask-WTF 的 csrf_token 必须显式写在模板里
别依赖 form.hidden_tag() 自动包裹,它容易被绕过:render_kw 改字段名、手动定义空 csrf_token 字段、或继承错的父类(比如用 Form 而非 FlaskForm)都会导致 token 不生成或为空。
- 最稳写法是单独写
{{ form.csrf_token }},放在<form></form>内部任意位置 - 如果表单是纯 HTML 手写(没用 WTForms),就得手动加:
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">,前提是后端已把csrf_token传入模板 - 确保
SECRET_KEY已配置,且 session backend(如 Redis 或 filesystem)可用;否则generate_csrf()静默失败,form.csrf_token渲染出来是空字符串
AJAX POST 请求必须手动提取并携带 csrf_token
浏览器不会自动把隐藏字段塞进 JSON 请求体,fetch 或 axios 发 application/json 请求时,Django/Flask-WTF 都收不到 token —— 这是 400 错误最常见源头。
- 前端取值:用
document.querySelector('input[name="csrf_token"]').value,别从 URL 或响应体里捞 - 发 JSON 请求时,把 token 加进 payload:
{"username": "a", "csrf_token": "abc123"} - 后端不能只调
form.validate_on_submit();得先用request.get_json()拿数据,再用MultiDict构造表单实例:form = LoginForm(MultiDict(request.get_json())) - 检查 Network 面板:请求头
Content-Type是application/x-www-form-urlencoded还是application/json?后者必须走手动解析路径
CSRF Token 生命周期必须和 session 强绑定
每次新页面加载都要刷新 session['csrf_token'],否则重放攻击可绕过;token 泄露渠道没堵住,再随机也白搭。
立即学习“Python免费学习笔记(深入)”;
- Set-Cookie 响应头必须带
HttpOnly=True; Secure=True; SameSite=Lax(开发环境Secure可关,上线必须开) - 绝不能把
csrf_token塞进 URL 参数(如?token=xxx),会被代理、日志、Referer 泄露 - API 返回 JSON 时,别把
csrf_token明文塞进响应体;前端若缓存或打印该字段,XSS 一触发就全暴露 - token 生成必须用
secrets.token_urlsafe(32),禁用random模块——后者不具备密码学安全性
真正难防的不是“怎么加 token”,而是 token 是否随 session 生效、是否被前端无意泄露、是否在每次敏感操作前强制刷新——这些细节漏掉一个,防护就形同虚设。


















