在 Web 开发中,表单(Form) 是用户与服务器交互的核心方式(如登录、注册、搜索)。
在 Flask 中处理表单主要有两种流派:
- 原生方式:直接使用
request.form获取数据。简单直接,但需要手动处理验证和安全问题。 - 扩展方式(推荐):使用 Flask-WTF。它提供了表单类定义、自动数据验证和 CSRF 保护,是工业级开发的标准选择。
⚠️ 核心安全概念:CSRF
跨站请求伪造 (CSRF) 是一种常见攻击。攻击者诱导用户在已登录的状态下点击恶意链接,从而以用户身份执行非预期操作。
- 防御方法:在每个表单中加入一个隐藏的、随机的 Token(令牌),服务器验证该 Token 是否合法。
- Flask-WTF 会自动帮你生成和验证这个 Token。
方案一:原生表单处理(理解底层)
适合极简场景或学习原理。
1. HTML 模板 (templates/login.html)
<form method="POST" action="/login">
<!-- 注意:原生方式没有 CSRF Token,存在安全隐患 -->
<input type="text" name="username" placeholder="用户名">
<input type="password" name="password" placeholder="密码">
<button type="submit">登录</button>
</form>2. Python 视图
from flask import request, render_template
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
# 1. 获取数据
username = request.form.get('username')
password = request.form.get('password')
# 2. 手动验证
if not username or not password:
return "请输入完整信息", 400
# 3. 业务逻辑...
return f"欢迎, {username}"
return render_template('login.html')方案二:使用 Flask-WTF(⭐ 最佳实践)
这是 Flask 社区推荐的方式。它将表单抽象为 Python 类,代码更整洁、更安全。
1. 安装与配置
pip install Flask-WTF必须配置 SECRET_KEY(用于加密 CSRF Token):
app.config['SECRET_KEY'] = 'your-secret-key-change-in-production'2. 定义表单类 (forms.py)
我们将表单结构定义为类,并添加验证规则。
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo
class RegistrationForm(FlaskForm):
# 字段定义:标签名, 验证器列表
username = StringField('用户名', validators=[
DataRequired(message='用户名不能为空'),
Length(min=3, max=20, message='长度需在3-20个字符之间')
])
email = StringField('邮箱', validators=[
DataRequired(),
Email(message='请输入有效的邮箱地址')
])
password = PasswordField('密码', validators=[DataRequired()])
confirm_password = PasswordField('确认密码', validators=[
DataRequired(),
EqualTo('password', message='两次密码输入不一致')
])
remember = BooleanField('记住我')
submit = SubmitField('注册')3. 视图函数处理 (routes.py)
Flask-WTF 提供了 validate_on_submit() 方法,它会自动判断是否是 POST 请求并执行所有验证。
from flask import render_template, redirect, url_for, flash
from app.forms import RegistrationForm
@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm()
# 如果请求是 POST 且所有验证都通过
if form.validate_on_submit():
# 获取清洗后的数据
username = form.username.data
email = form.email.data
# TODO: 保存用户到数据库
flash('注册成功!请登录。', 'success')
return redirect(url_for('login'))
# 如果是 GET 请求,或者验证失败,渲染表单并显示错误
return render_template('register.html', form=form)4. 模板渲染 (templates/register.html)
在 Jinja2 中渲染表单对象。
<form method="POST" action="">
<!-- ⚠️ 关键:生成 CSRF 隐藏令牌 -->
{{ form.hidden_tag() }}
<div>
{{ form.username.label }}<br>
{{ form.username(size=32) }}<br>
<!-- 显示验证错误 -->
{% for error in form.username.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</div>
<div>
{{ form.email.label }}<br>
{{ form.email(size=32) }}<br>
{% for error in form.email.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</div>
<div>
{{ form.password.label }}<br>
{{ form.password(size=32) }}
</div>
<div>
{{ form.confirm_password.label }}<br>
{{ form.confirm_password(size=32) }}
</div>
<div>
{{ form.remember() }} {{ form.remember.label }}
</div>
<div>
{{ form.submit() }}
</div>
</form>📂 文件上传处理
文件上传是表单的特殊情况,因为数据不是文本,而是二进制流。
关键点:
- HTML 表单必须设置
enctype="multipart/form-data"。 - 使用
request.files获取文件。 - 安全第一:永远不要信任客户端传来的文件名,使用
secure_filename清洗。
import os
from werkzeug.utils import secure_filename
from flask import request, current_app
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return 'No file part', 400
file = request.files['file']
if file.filename == '':
return 'No selected file', 400
if file and allowed_file(file.filename):
# 清洗文件名,防止路径穿越攻击 (如 ../../etc/passwd)
filename = secure_filename(file.filename)
# 保存文件
save_path = os.path.join(current_app.config['UPLOAD_FOLDER'], filename)
file.save(save_path)
return 'File uploaded successfully', 201
return 'Invalid file type', 400(注:Flask-WTF 也有 FileField 和 FileAllowed 验证器,可以更方便地集成到表单类中。)
💡 最佳实践总结
- 始终使用 Flask-WTF:它能帮你处理 CSRF 保护和复杂的数据验证逻辑。
- PRG 模式 (Post/Redirect/Get):表单提交成功后,务必重定向到一个新的页面(如首页或登录页),而不是直接返回 HTML。这样可以防止用户刷新页面时重复提交表单。
- Flash 消息:使用
flash()函数在重定向前存储一次性消息,在下一个页面显示“操作成功”或“验证失败”。 - 文件安全:
- 限制文件扩展名。
- 使用
secure_filename。 - 最好将上传的文件存储在非 Web 根目录,或通过 Nginx 专门服务静态文件。
- 不要硬编码验证逻辑:将验证规则放在
validators列表中,保持视图函数干净。
掌握了表单处理,你的应用就能真正接收用户输入了。接下来,你想学习如何将这些数据持久化存储到数据库 (Flask-SQLAlchemy),还是学习如何实现用户登录会话管理 (Flask-Login)?