Flask 表单提交启用 CSRF 保护最推荐的做法是安装 Flask-WTF 扩展并初始化 CSRFProtect,适用于所有需要防止跨站请求伪造的 Web 应用,必须确保配置了 SECRET_KEY 且在模板中正确渲染令牌。
先说结论:Flask 默认不带 CSRF 防护,必须通过 Flask-WTF 扩展实现,缺一不可的三要素是服务端生成 token、模板显式渲染、提交原样回传。
- 先判断:确认项目已安装 Flask-WTF 且未禁用全局 CSRF 检查。
- 优先做:配置 SECRET_KEY 密钥并在视图初始化 CSRFProtect(app)。
- 再验证:提交表单观察是否报 400 错误,确认 Network 标签页请求头包含 Token。
快速处理思路
通过 pip 安装扩展后,在应用工厂函数中初始化保护对象,并在 HTML 表单中插入隐藏字段。
# 安装扩展
pip install Flask-WTF
# 初始化保护
from flask_wtf.csrf import CSRFProtect
app.config['SECRET_KEY'] = 'your-secret-key'
csrf = CSRFProtect(app)
# 模板中使用
<form method="post">
{{ form.hidden_tag() }}
<input type="submit" value="Submit">
</form>为什么会这样
CSRF 攻击利用浏览器自动携带 Cookie 的特性,服务器无法区分请求是否来自用户本人。Flask-WTF 通过为每个会话生成唯一令牌,要求提交时原样回传,从而验证请求合法性。若令牌缺失或不匹配,服务器将拒绝处理非幂等请求(如 POST、PUT、DELETE)。
分步处理
按顺序执行以下配置,确保令牌生成、渲染和验证链路完整。
1. 安装与基础配置
安装 Flask-WTF 后,必须在 app.config 中设置 SECRET_KEY,否则 generate_csrf() 会静默失败。推荐使用 secrets 模块生成强随机密钥。
app.config['SECRET_KEY'] = 'your-secret-key'
csrf = CSRFProtect(app)2. 模板渲染令牌
在 Jinja2 模板中,使用{{ form.hidden_tag() }}或单独写{{ form.csrf_token }}。避免手动重写字段名导致 hidden_tag() 无法识别。
3. AJAX 请求处理
浏览器不会自动将隐藏字段塞进 JSON 请求体。前端需手动从 DOM 取值并放入请求头 X-CSRFToken 或 payload 中。
var csrf_token = "{{ csrf_token() }}";
$.ajaxSetup({
beforeSend: function(xhr, settings) {
xhr.setRequestHeader("X-CSRFToken", csrf_token);
}
});4. 细粒度控制
若某些视图不需要保护,可使用@csrf.exempt 装饰器临时关闭,或配置 WTF_CSRF_CHECK_DEFAULT 为 False 后手动启用。
怎么验证是否生效
通过浏览器开发者工具观察网络请求和响应状态,确认令牌机制正常工作。
1. 检查响应状态码
尝试不带 token 提交 POST 请求,服务器应返回 400 Bad Request 且报错信息包含"The CSRF token is missing"。
2. 检查请求头与 Payload
在 Network 标签页查看提交请求,确认 Header 中存在 X-CSRFToken 或 Form Data 中包含 csrf_token 字段。
3. 检查 validate_on_submit
在后端日志中确认 form.validate_on_submit() 返回 True,若返回 False 且无其他验证错误,通常是 CSRF 验证未通过。
常见坑
以下场景容易导致 CSRF 保护失效或引发 400 错误,配置时需特别注意。
- SECRET_KEY 未配置:导致令牌生成失败,validate_on_submit() 静默返回 False。
- AJAX 内容类型错误:发送 application/json 时,Flask-WTF 默认不从 request.json 解析 token,需手动构造表单或调整配置。
- hidden_tag 被绕过:若表单类显式定义了 csrf_token 字段但未设 default=generate_csrf,令牌值将为空。
- 路由方法限制:视图只写了['POST'] 可能导致 GET 请求进不来,表单实例化时缺少 request 上下文。
常见问题
validate_on_submit() 总是返回 False 怎么定位?
先查 SECRET_KEY 是否配置、路由 methods 是否包含 GET、模板 action 属性是否指向正确 endpoint。
AJAX 提交为什么报 400 错误?
浏览器不会自动把隐藏字段塞进 JSON 请求体,前端必须手动从 DOM 取值并加进 payload 或请求头。
如何临时关闭某个路由的 CSRF 保护?
在不需要保护的路由上加上@csrf.exempt 装饰器,或生成表单时加入参数 form = Form(csrf_enabled=False)。
参考来源
- Flask 中如何防止跨站请求伪造 (CSRF)?
- 如何在 Python Flask 中实现严谨的 CSRF 跨站请求伪造防御?
- Flask-WTF 项目中的 CSRF 保护机制详解
- Flask CSRF Token 保护机制详解。
- flask csrf 保护的使用技巧 - 岚漾忆雨 - 博客园
- Flask 中的 CSRF 保护