Flask 中 CSRF 防护的完整实现与常见陷阱详解

夏萱大大_4049

夏萱大大_4049

2026-09-26

542人浏览

原创

Flask 中 CSRF 防护的完整实现与常见陷阱详解

本文系统讲解 flask 应用中 csrf 防护的正确实现方式:涵盖服务端令牌生成、模板嵌入、表单提交、ajax 请求处理、token 生命周期管理及关键安全配置,指出仅校验存在性而不比对值的典型错误,并提供可直接落地的代码范式。

本文系统讲解 flask 应用中 csrf 防护的正确实现方式:涵盖服务端令牌生成、模板嵌入、表单提交、ajax 请求处理、token 生命周期管理及关键安全配置,指出仅校验存在性而不比对值的典型错误,并提供可直接落地的代码范式。

CSRF(跨站请求伪造)攻击的本质,是利用用户已认证的会话状态,诱使其在不知情下执行非预期操作——例如点击恶意页面上的“领取红包”按钮,实际却触发了银行转账。Flask 默认完全不提供 CSRF 防护,request.form 或 request.json 的裸调用等同于将敏感操作接口直接暴露给任意第三方站点。真正的防护必须依赖「三要素闭环」:服务端安全生成 → 前端可靠传递 → 后端严格校验,缺一不可。

✅ 正确的 Token 生成与绑定机制

Token 必须具备密码学强度、与用户会话强绑定、且每次新页面加载均刷新。严禁使用 random 模块(如 random.randint()),因其不具备不可预测性;必须使用 secrets 模块:

from flask import Flask, session, render_template, request, abort
import secrets

app = Flask(__name__)
app.secret_key = "your-32-byte-secret-here"  # 必须配置!否则 session 失效

@app.before_request
def ensure_csrf_token():
    """为每个新请求/页面加载生成并刷新 token"""
    if 'csrf_token' not in session:
        session['csrf_token'] = secrets.token_urlsafe(32)  # 43 字符 URL 安全字符串
    # 注意:不要在 POST 处理中重置 token,否则导致“一次有效”逻辑失效

⚠️ 关键提醒:session['csrf_token'] 必须在 before_request 或视图中显式设置,且确保 SECRET_KEY 已配置、session backend(如 Redis)可用;否则 session 写入静默失败,前端拿到的 {{ csrf_token }} 将为空字符串。

✅ 模板中安全嵌入 Token(两种场景)

场景一:使用 Flask-WTF 表单(推荐)

务必显式写出 {{ form.csrf_token }},切勿依赖 form.hidden_tag() —— 后者可能因字段重命名、空字段定义或继承错误(如误用 Form 而非 FlaskForm)导致 token 缺失:

<!-- login.html -->
{{ form.username.label }} {{ form.username() }} {{ form.password.label }} {{ form.password() }} {{ form.csrf_token }} {# ✅ 强制显式渲染 #}

场景二:纯 HTML 表单(无 WTForms)

需手动插入隐藏字段,并确保后端已将 token 传入模板上下文:

Flask 3.0.2
Flask 3.0.2

Flask 3.0.2 是 Flask 的官方历史稳定版本,下载地址使用 PyPI wheel 包直链,适合指定版本安装和项目环境复现。

下载
@app.route('/login')
def login_page():
    return render_template('login.html', csrf_token=session.get('csrf_token', ''))
<!-- login.html -->
{# ✅ 手动注入 #}

✅ 后端严格校验逻辑(核心防线)

校验必须同时满足两个条件:字段存在 + 值精确匹配。仅检查 isset(如 PHP 中 !isset($_POST['token']))或仅比对空值,形同虚设:

@app.route('/login', methods=['POST'])
def handle_login():
    # ✅ 正确校验:存在性 + 精确相等(恒定时间比较更佳)
    submitted_token = request.form.get('csrf_token')
    if not submitted_token or not secrets.compare_digest(submitted_token, session.get('csrf_token', '')):
        abort(403)  # Forbidden

    # ✅ 校验通过后,才进行业务逻辑(如验证用户名密码)
    username = request.form['username']
    password = request.form['password']
    # ... 认证逻辑
    return "登录成功"

? secrets.compare_digest() 是关键:它防止时序攻击(timing attack),避免攻击者通过响应时间差异推测 token 字符。

✅ AJAX / JSON 请求的特殊处理

浏览器不会自动将 <input name="csrf_token"> 值注入 application/json 请求体,这是 400 错误最高发场景。必须手动提取并携带:

// 前端:从 DOM 获取 token(非 URL 或响应体!)
const csrfToken = document.querySelector('input[name="csrf_token"]').value;

// 发送 JSON 请求
fetch('/api/transfer', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    amount: 1000,
    to_account: 'xxx',
    csrf_token: csrfToken  // ✅ 显式加入 payload
  })
});
# 后端:无法直接用 form.validate_on_submit()
@app.route('/api/transfer', methods=['POST'])
def api_transfer():
    data = request.get_json()
    if not data or not secrets.compare_digest(
        data.get('csrf_token', ''), 
        session.get('csrf_token', '')
    ):
        abort(403)

    # ✅ 使用 MultiDict 构造表单对象(若需复用 WTForms 验证)
    from werkzeug.datastructures import MultiDict
    form_data = MultiDict(data)  # 将 dict 转为类表单结构
    form = TransferForm(form_data)
    if not form.validate():
        abort(400)

    # 执行转账...
    return {"status": "success"}

⚠️ 必须规避的安全反模式

  • ❌ Token 放入 URL 参数(如 /delete?id=123&token=abc)→ 被代理、CDN、Referer、服务器日志明文记录;
  • ❌ API 响应体中返回 csrf_token → 若前端缓存或 console.log() 输出,XSS 一触即发;
  • ❌ Token 与 session 解耦(如存入数据库独立表)→ 丧失会话绑定性,重放攻击可绕过;
  • ❌ Cookie 缺少安全属性 → 必须设置 HttpOnly=True; Secure=True; SameSite=Lax(生产环境 Secure=True 不可省略);
  • ❌ 使用弱随机源(random, time.time())→ secrets.token_urlsafe(32) 是唯一合规选择。

✅ 总结:CSRF 防护检查清单

项目 正确做法 验证方式
服务端生成 secrets.token_urlsafe(32) + 绑定 session 查看 Network → Response Headers 中 Set-Cookie 是否含 SameSite=Lax
前端传递 HTML 表单:<input hidden>;AJAX:手动注入 JSON payload 浏览器 Elements 面板确认隐藏字段存在且非空
后端校验 secrets.compare_digest(submitted, session['csrf_token']) 故意篡改 token 提交,应返回 403
会话安全 app.secret_key 配置 + SESSION_COOKIE_SECURE=True(生产) curl -I https://yoursite.com 检查 Set-Cookie 头

CSRF 防护不是“开了就安全”的开关,而是贯穿请求生命周期的精密链条。每一次页面加载刷新 token、每一次提交强制双向比对、每一个 JSON 请求手动携带——这些看似琐碎的步骤,共同构筑了抵御会话劫持的第一道可信屏障。

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

flask

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

2023.07.20

1591

4

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

2023.07.25

3824

7

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.07.31

1589

3

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

2023.08.03

21977

23

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

2707

5

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

2747

5

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

1103

5

python合并两个列表
python合并两个列表

Python是一种强大的编程语言,具有许多方便的功能和工具。在Python中,有多种方法可以合并两个列表。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.10

596

4

python是前端还是后端
python是前端还是后端

Python属于前端也属于后端,其灵活性和丰富的生态系统使得开发人员能够在不同的领域中灵活运用。本专题为大家提供python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

2123

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Flask-Migrate数据库迁移文档
Flask-Migrate数据库迁移文档

共0课时 | 0人学习

Flask官方快速入门文档
Flask官方快速入门文档

共0课时 | 0人学习