Flask-WTF是Flask与WTForms的简单集成,是Flask的一个扩展库,实现的功能如下:
- 集成了WTForms;
- 使用CSRF token保护表单(防止CSRF);
- 全球CSRF保护;
- 支持reCAPTCHA(验证码);
- 使用Flask-Uploads支持文件上传;
- 使用Flask-Babel提供国际化。
CSRF:Cross-Site Request Forgery简写,跨站请求伪造。Flask-WTF封装了防止CSRF功能。原理可参考博客。
WTForms:是一个用于Python Web开发的提供灵活的表单验证和渲染的库。
表单:在网页中主要负责数据采集功能。一个表单有3个基本组成部分(表单标签、表单域、表单按钮)。
flask_wtf源码 目录结构:
├── _compat.py
├── csrf.py ----防止CSRF
├── file.py ----文件上传
├── form.py ----表单
├── html5.py
├── i18n.py ----国际化
├── __init__.py
└── recaptcha ----验证码
├── fields.py
├── __init__.py
├── validators.py
└── widgets.py
1、安装
不作过多讲述
像其他Flask扩展一样,安装时名称是flask-wtf
,import
时是flask_wtf
,注意横线、下划线,这是惯例。
2、表单
创建表单主要是3方面:- 安全表单
- 文件上传(表单)
- reCAPTCHA(表单)
2.1、安全表单
2.1.1、创建表单
from flask_wtf import FlaskForm # 从flask_wtf包中的__init__.py导入这个FlaskForm类,来自forms.py模块
from wtforms import StringField # 从wtforms包导入StringField类
from wtforms.validatiors import DataRequired
class MyForm(FlaskForm):
name = StringField('name', validators=[DataRequired()])
注意:从flask-wtf 0.9.0开始,Flask-WTF不再从wtforms import任何内容,因此必须开发者自己从wtforms去导入(如StringField等)
另外,CSRF token 会被自动创建,我们可以在模板中渲染它:
<form method="POST" action="/">
{{ form.csrf_token }}
{{ form.name.label }} {{ form.name(size=20) }}
<input type="submit" value="Go">
</form>
假如在表单中有多个隐藏字段(??),可在块中使用hidden_tag():
<form method="POST" action="/">
{{ form.hidden_tag() }}
{{ form.name.label }} {{ form.name(size=20) }}
</form>
默认情况下:
FlaskForm就是一个具有CSRF保护的session安全表单了。如果不想CSRF保护,可传入:
form = FlaskForm(csrf_enabled=False)
也可以在全局上配置它,但不建议这么做:
WTF_CSRF_ENABLED = False
为了生成csrf token,我们必须有一个secret key,通常它与我们的Flask app的secret key是相同的。但是若想设置一个不同的,也可以:
WTF_CSRF_SECRET_KEY = ' a random string'
2.1.2、验证表单
在view handler中验证请求:
@app.route('/submit', methods=('GET', 'POST'))
def submit():
form = MyForm()
if form.validate_on_submit():
return redirect('/success')
return render_template('submit.html', form=form)
注意:我们不必将request.form
传递给Flask-WTF,因为它将自动load;便捷的是validate_on_submit
将检查它是否为一个POST请求、以及是否有效。
2.2、文件上传
2.2.1、创建文件上传表单
Flask-WTF提供的FileField类 不同于WTForms提供的。它会检查文件是一个FileStorage的非空实例,另外data将是None。
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileRequired
from werkzeug.utils import secure_filename
class PhotoForm(FlaskForm):
photo = FileField(validators=[FileRequired()])
@app.route('/upload', methods=['GET', 'POST'])
def unpload():
if form.validate_on_submit():
f = form.photo.data
filename = secure_filename(f.filename)
f.save(os.path.join(
app.instance_path, 'photos', filename
))
return redirect(url_for('index'))
return render_template('upload.html', form=form)
记得将HTML模板 form标签的enctype
设置为multipart/form-data
,另外request.files
为空:
<form method="POST" enctype="multipart/form-data">
...
</form>
Flask-WTF handler将会为我们将form data传递给表单。如果想在data中显式地传递,记得request.form
必须结合request.files
,这样让表单看到file data。
form = PhotoForm()
# 等价于
from flask import request
from werkzeug.datastructures import CombinedMultiDict
form = PhotoForm(CombinedMultiDict((request.files, request.form)))
2.2.2、验证表单
Flask-WTF通过FileRequired
和FileAllowed
验证文件上传。他它们使用Flask-WTF和WTForms的FileField
类都行。FileAllowed
将和Flask-Uploads一起工作。
from flask_uploads import UploadSet, IMAGES
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed, FileRequired
images = UploadSet('images', IMAGES)
class UploadForm(FlaskForm):
upload = FileField('image', validators=[
FileRequired(), FileAllowed(images, 'Images only!')
])
也可以直接通过传递扩展名,而不使用Flask-Uploads:
class UploadForm(FlaskForm):
upload = FileField('image', validators=[
FileRequired(),
FileAllowed(['jpg', 'png'], 'Images only!')
])
2.3、reCAPTCHA(验证码)
Flask-WTF 通过RecaptchaField
提供了reCAPTCHA:
from flask_wtf import FlaskForm, RecaptchaField
from wtforms import TextField
class SignupForm(FlaskForm):
username = TextField('Username')
recaptcha = RecaptchaField()
下方提供了一些配置,得实现它们:
RECAPTCHA_PUBLIC_KEY | required A public key. |
RECAPTCHA_PRIVATE_KEY | required A private key. |
RECAPTCHA_API_SERVER | optional Specify your Recaptcha API server. |
RECAPTCHA_PARAMETERS | optional A dict of JavaScript (api.js) parameters. |
RECAPTCHA_DATA_ATTRS | optional A dict of data attributes options. https://developers.google.com/recaptcha/docs/display |
以下是RECAPTCHA_PARAMETERS
和RECAPTCHA_DATA_ATTRS
的示例:
RECAPTCHA_PARAMETERS = {'hl': 'zh', 'render': 'explicit'}
RECAPTCHA_DATA_ATTRS = {'theme': 'dark'}
为了测试应用程序,假如app.testing
为True
,recaptcha字段必须总是有效。
它可以在模板中很容易地设置:
<form action="/" method="post">
{{ form.username }}
{{ form.recaptcha }}
</form>
3、CSRF保护
任何使用FlaskForm来处理请求的视图都已经得到CSRF的保护。如果我们有不使用FlaskForm 或创建Ajax请求的视图,也可以使用所提供的CSRF扩展来保护这些请求的。
3.1、setup
若要对一个Flask应用程序全局提供CSRF保护,可注册CSRFProtect
扩展。如下:
from flask_wtf.csrf import CSRFProject
csrf = CSRFProtect(app)
也可以像其他Flask扩展一样,这样使用:
csrf = CSRFProject()
def create_app():
app = Flask(__name__)
csrf.init_app(app)
注意:CSRF保护需要一个密钥来安全地签名token。默认情况下,它会使用Flask应用程序的SECRET_KEY
。当然也可以单独地设置WTF_CSRF_SECRET_KEY
。
3.2、HTML Forms
当在使用一个FlaskForm时,可以像正常一样渲染表单的CSRF字段:
<form method="post">
{{ form.csrf_token }}
</form>
如果模板没有使用FlaskForm,可以这样在表单中渲染一个带有token的隐藏输入:
<form method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
</form>
3.3、JavaScript Requests
当发送一个AJAX请求时,可以添加X-CSRFToken header给它。例如,在jQuery中,可以配置所有请求去发送这个token:<script type="text/javascript">
var csrf_token = "{{ csrf_token() }}";
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrf_token);
}
}
});
</script>
3.4、自定义错误响应
当CSRF验证失败时,将引发CSRFError异常。默认情况下,会返回一个有失败原因和400状态码的响应。我们也可以使用Flask的errorhandler()
来自定义错误响应:
from flask_wtf.csrf import CSRFError
@app.errorhandler(CSRFError)
def handle_csrf_error(e):
return render_template('csrf_error.html', reason=e.description), 400
3.5、给某些视图取消CSRF保护
强烈建议给所有视图使用CSRF保护。但是,也可以使用装饰器排除一些视图:
@app.route('/foo', methods=('GET', 'POST'))
@csrf.exempt
def my_handler():
# ...
return 'ok'
也可以取消蓝图下的所有视图 保护:
csrf.exempt(account_blueprint)
也可以通过默认方式在所有视图里设置CSRF保护为不可用,即 WTF_CSRF_CHECK_DEFAULT=False。也可以在需要时,选择调用protect()
。还可以在检查CSRF token前对请求进行一些预处理:
@app.before_request
def check_csrf():
if not is_oauth(request):
csrf.protect()
参考文档:
1、官方
2、github 包含所有历史版本的源码
3、WTForms官网
4、WTForm github源码
5、官方实例
6、开发者接口----其实就是源码
附:WTForms 思维导图(其中最常用的)