Flask 表单提交如何启用 CSRF 保护防止跨站请求伪造

文章导读
Flask 表单提交启用 CSRF 保护最推荐的做法是安装 Flask-WTF 扩展并初始化 CSRFProtect,适用于所有需要防止跨站请求伪造的 Web 应用,必须确保配置了 SECRET_KEY 且在模板中正确渲染令牌。
📋 目录
  1. A 快速处理思路
  2. B 为什么会这样
  3. C 分步处理
  4. D 怎么验证是否生效
  5. E 常见坑
  6. F 常见问题
  7. G 参考来源
A A

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 后手动启用。

Flask 表单提交如何启用 CSRF 保护防止跨站请求伪造

怎么验证是否生效

通过浏览器开发者工具观察网络请求和响应状态,确认令牌机制正常工作。

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 保护