基于 Flask-WTF 的 CSRF 防护机制与实现
跨站请求伪造(CSRF)是一种常见的 Web 安全威胁,攻击者通过诱导已认证用户访问恶意链接,利用其有效会话执行非预期操作。Flask-WTF 扩展为 Flask 应用提供了内建的 CSRF 防御能力,无需手动实现令牌生成与校验逻辑。
安装依赖
pip install flask-wtf
核心防护原理
CSRF 防护的核心在于验证请求来源的合法性。服务端为每个用户会话生成唯一且不可预测的令牌,前端提交表单时需携带该令牌,服务端校验通过后方可执行后续逻辑。
服务端集成步骤
配置密钥
密钥用于加密 CSRF 令牌,可通过直接赋值或配置类方式设置。
# 直接设置
app.secret_key = "your-random-string-here"
# 或通过配置类
class AppConfig(object):
DEBUG = True
SECRET_KEY = "dsad32DASSLD*13%^32"
app.config.from_object(AppConfig)
初始化 CSRF 保护
提供两种初始化模式:延迟绑定与立即绑定。
# 模式一:延迟绑定(推荐,避免循环引用)
from flask_wtf import CSRFProtect
csrf_guard = CSRFProtect()
def create_app():
app = Flask(__name__)
# ... 加载配置
csrf_guard.init_app(app)
return app
# 模式二:立即绑定
# CSRFProtect(app)
完整应用示例
from flask import Flask, render_template, request, session, redirect, url_for
from flask_wtf import CSRFProtect
from flask_wtf.csrf import generate_csrf
app = Flask(__name__, template_folder="templates", static_folder="static")
# 应用配置
app.config.update({
"DEBUG": True,
"SECRET_KEY": ":123.,2,s,"
})
# 启用 CSRF 防护
CSRFProtect(app)
@app.route("/")
def home():
page_title = "网站首页"
return render_template("index.html", **locals())
@app.route("/login", methods=["GET", "POST"])
def authenticate():
page_title = "用户登录"
if request.method == "GET":
csrf_value = generate_csrf()
return render_template("login.html", **locals())
# POST 请求自动经过 CSRF 校验
account = request.form.get("username")
secret = request.form.get("password")
if account == "xiaoming" and secret == "123456":
session["current_user"] = "xiaoming"
session["auth_status"] = True
return "登录成功!<br><a href='/'>返回首页</a>"
return "登录失败!<br>><a href='/login'>重新登录</a>"
if __name__ == '__main__':
app.run()
前端模板配置
表单嵌入 CSRF 令牌
在 HTML 表单中通过隐藏字段传递令牌,模板引擎渲染时自动填充有效值。
<!-- login.html -->
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{{ page_title }}</title>
</head>
<body>
<form action="/login" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_value }}">
<label>登录账号:</label>
<input type="text" name="username" required>
<br><br>
<label>登录密码:</label>
<input type="password" name="password" required>
<br><br>
<button type="submit">登录</button>
</form>
</body>
</html>
首页模板
<!-- index.html -->
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{{ page_title }}</title>
</head>
<body>
{% if session.get("auth_status") %}
<a href="/user">欢迎回来,{{ session["current_user"] }}</a>
{% else %}
<a href="{{ url_for('authenticate') }}">登录</a>
{% endif %}
</body>
</html>
关键注意事项
- 所有状态变更操作(POST/PUT/DELETE)务必启用 CSRF 验证
- 密钥需保持足够复杂度且妥善保管,避免硬编码于版本控制
- AJAX 请求需在请求头中携带
X-CSRFToken字段 - 生产环境建议配合 HTTPS 使用,防止令牌传输过程中被截获
