Flask-WTF的CSRF保护并非默认全局启用,需显式调用csrf.init_app(app)才能生效;表单校验自动开启,但API等非表单请求须手动配置豁免或白名单校验。

Flask-WTF 默认 CSRF 保护怎么开
Flask-WTF 的 CSRFProtect 不是“默认开启”,而是“只要用了 FlaskForm 就自动启用表单级校验”,但全局拦截(比如非表单的 POST/PUT/DELETE 请求)得手动挂载中间件。很多人以为装了 Flask-WTF 就万事大吉,结果 API 接口照样被 CSRF 攻击绕过。
实操上,必须显式初始化并注册到 app:
from flask_wtf.csrf import CSRFProtect <p>csrf = CSRFProtect() csrf.init_app(app) <!-- 注意:不是 app.config['WTF_CSRF_ENABLED'] = True --></p>
-
WTF_CSRF_ENABLED只控制表单类是否生成/校验 token,不影响全局请求拦截 - 没调用
csrf.init_app(app),CSRFProtect根本不生效,连 400 错误都不会抛 - 如果用了蓝本(Blueprint),也要确保
init_app在所有蓝本注册前调用,否则部分路由漏保护
跨域请求怎么白名单豁免 CSRF 校验
前后端分离时,前端在 https://fe.example.com,后端在 https://api.example.com,浏览器发带 Content-Type: application/json 的 POST 请求——这种请求不走表单提交流程,CSRFProtect 默认会拦,但你又不能关全局保护。
正确做法是按请求特征动态跳过校验,而不是关掉整个中间件:
立即学习“Python免费学习笔记(深入)”;
- 用
@csrf.exempt装饰器标记特定视图函数,只对那些明确接受跨域 JSON 请求的 endpoint 生效 - 不要 exempt 整个蓝本或用通配路径,容易误放行敏感操作(比如
/api/user/delete) - 更安全的做法是写自定义检查逻辑:只在
request.headers.get('Origin')属于预设白名单、且request.is_json为真时跳过
示例:
@app.route('/api/data', methods=['POST'])
@csrf.exempt
def api_data():
return {'ok': True}
CSRF token 怎么传给前端才安全
token 不是塞进 HTML 模板里就完事了。常见错误是把 csrf_token() 渲染成隐藏字段后,前端 JS 直接读 DOM 取值——这在 CSP 严格策略下可能被禁,或被 XSS 劫持。
- 推荐方式:后端在响应头中返回
X-CSRF-Token,前端 JS 从 header 读取(response.headers.get('X-CSRF-Token')) - 如果必须放 body,确保只在首次加载页面时由 Jinja 渲染一次,且不暴露在可被 XSS 注入的上下文中
- 不要把 token 存 localStorage 或 cookie(除非
HttpOnly=False且SameSite=None配合 CORS,但这反而增加风险) - 注意:Flask-WTF 的
generate_csrf()函数返回的是 raw token,不是 base64 编码后的字符串,前端需原样携带
为什么有些 POST 请求 400 却没报错信息
典型现象:前端发了个 POST,后端直接返回 400,但日志没记录、response body 空,debug 时一脸懵。大概率是 CSRFProtect 在 request context 创建阶段就失败了,根本没走到视图函数。
- 原因常是:请求没带
X-CSRF-Token头,或 cookie 中的csrf_token不存在 / 过期 / 域名不匹配 - Flask-WTF 默认不输出具体失败原因,避免泄露防护机制细节;可通过设置
WTF_CSRF_CHECK_DEFAULT为 False + 自定义 error handler 来调试 - 特别注意:Nginx 或 CDN 如果 strip 了某些 header(如
X-CSRF-Token),或者设置了SameSite=Lax导致跨域 cookie 不发送,都会触发静默 400
真正难搞的不是加保护,而是保护和跨域共存时,header、cookie、origin、samesite 四者之间的对齐——少一个条件,token 就失效,而且失效得悄无声息。


















