flask-form文档

Form表单是Web应用中最基础的一部分。为了能处理Form表单,Flask-WTF扩展提供了良好的支持。

0x00 快速回忆

  1. 首先在模板html里面定义form的使用,例如:
1
2
3
4
5
6
<form method="POST">
<!--启动CSRF-->
{{form.hidden_tag()}}
<p>
用户:{{form.name(size=20,id='name')}}
{%for e in form.name.errors%}
  1. 然后定义一个表单类,继承与Form类:

    1
    2
    3
    4
    5
    6
    7
    8
    #登录表单类,继承与Form类
    class BaseLogin(Form):
    #用户名
    name=StringField('name',validators=[DataRequired(message=u"用户名不能为空")
    ,Length(10,20,message=u'长度位于10~20之间')],render_kw={'placeholder':u'输入用户名'})
    #密码
    password=PasswordField('password',validators=[DataRequired(message=u"密码不能为空")
    ,Length(10,20,message=u'长度位于10~20之间')],render_kw={'placeholder':u'输入密码'})
  2. 然后在处理模板的函数中(如route.py),将相应html中的form赋值成我们定义的表单类

    1
    2
    3
    4
    5
    6
    7
    #定义处理函数和路由规则,接收GET和POST请求
    @app.route('/baselogin',methods=('POST','GET'))
    def baselogin():
    form=BaseLogin()
    #判断是否是验证提交
    if form.validate_on_submit():
    #跳转

    我们从页面输入的数据就会通过路由中的设置经过表单类的验证

0x01 安装

1
pip install flask-wtf

0x02 开启CSRF保护

Flask-WTF提供了对所有Form表单免受跨站请求伪造(Cross-Site Request Forgery,CSRF)攻击的技术支持(通过添加动态token令牌的方式,关于CSRF可以自行在网上搜索相关内容,以后有时间可能会撰写相关内容)。

考虑到Flask扩展需要大量的配置信息,从这里开始,我们在在Flask根目录下新增config.py的配置文件:

img

启动CSRF保护,可以在config.py中定义2个变量:

1
2
CSRF_ENABLED = True
SECRET_KEY = '123456'

其中SECRET_KEY用来建立加密的令牌,用于验证Form表单提交,在自己编写应用程序时,可以尽可能设置复杂一些,这样恶意攻击者将很难猜到密钥值。在__init__.py文件中添加如下代码:

1
app.config.from_object('config')

此时完整的__init__.py文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!flask/bin/env python
#coding:utf-8

__author__ = 'kikay'

from flask import Flask
from flask.ext.bootstrap import Bootstrap

#定义app对象
app=Flask(__name__)
#定义Bootstrap对象
bootstrap=Bootstrap(app)
#启动配置文件
app.config.from_object('config')

from app import views

最后,我们需要在响应的html模板的Form表单中加上如下语句:

1
{{form.csrf_token}}

或者:

1
{{form.hidden_tag()}}

其中的form是views.py中对应处理函数传递过来的Form对象名称,根据具体情况会有所变化。通过上面的配置,我们就启动了CSRF保护。

0x03 WTF表单

下面看一个简单的登录表单:

(1)通常我们会把一个表单里面的元素定义为1个类。下面我们在app包下新建forms.py文件,专门用于定义表单的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!flask/bin/env python
#coding:utf-8

#引入Form基类
from flask.ext.wtf import Form
#引入Form元素父类
from wtforms import StringField,PasswordField
#引入Form验证父类
from wtforms.validators import DataRequired,Length

__author__ = 'kikay'

#登录表单类,继承与Form类
class BaseLogin(Form):
#用户名
name=StringField('name',validators=[DataRequired(message=u"用户名不能为空")
,Length(10,20,message=u'长度位于10~20之间')],render_kw={'placeholder':u'输入用户名'})
#密码
password=PasswordField('password',validators=[DataRequired(message=u"密码不能为空")
,Length(10,20,message=u'长度位于10~20之间')],render_kw={'placeholder':u'输入密码'})

(2)模板baselogin.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
.base_login{
float: none;
display: block;
margin-left: auto;
margin-right:auto;
width: 200px;
}
</style>
<title>BaseLogin</title>
</head>
<body>
<div class="base_login">
<h1>用户登录</h1>
<div>
<form method="POST">
<!--启动CSRF-->
{{form.hidden_tag()}}
<p>
用户:{{form.name(size=20,id='name')}}
{%for e in form.name.errors%}
<span style="color: red">*{{e}}</span>
{%endfor%}
</p>
<p>
密码:{{form.password(size=20,id='password')}}

{%for e in form.password.errors%}
<span style="color: red">*{{e}}</span>
{%endfor%}
</p>
<p><button style="float: right" type="submit">登录</button></p>
</form>
</div>
</div>
</body>
</html>

(3)views.py新增以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from flask import url_for
#导入定义的BaseLogin
from app.forms import BaseLogin

#定义处理函数和路由规则,接收GET和POST请求
@app.route('/baselogin',methods=('POST','GET'))
def baselogin():
form=BaseLogin()
#判断是否是验证提交
if form.validate_on_submit():
#跳转
flash(form.name.data+'|'+form.password.data)
return redirect(url_for('success'))
else:
#渲染
return render_template('baselogin.html',form=form)

@app.route('/success')
def success():
return '<h1>Success</h1>'

效果如下:

img

看下源代码:

img

红色框中就是生成的CSRF保护的token令牌值。如果填入不能通过验证的值,比如admin/admin,将显示警告信息:

img

如果输入admin12345678/admin12345678将发生跳转:

img

前面已经实现了一个基础的表单,一些基本元素也涉及到了,还可以使用 Bootstrap 中预先定义好的表单样式渲染整个 Flask-WTF 表单:

1
2
3
4
5
6
7
8
9
10
11
12
{%extends 'bootstrap/base.html'%}
{%import 'bootstrap/wtf.html' as wtf%}
{%block content%}
<div class="container col-lg-3 col-lg-offset-3">
<div class="page-header">
<h3>WTF-Login</h3>
</div>
<div>
{{wtf.quick_form(form)}}
</div>
</div>
{%endblock%}

其他部分的修改很简单,就不多讲。

0x04 Flash消息

在表单上面显示一个消息,提示用户用户名或密码错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
@app.route('/baselogin2', methods=('POST', 'GET'))
def baselogin2():
form = BaseLogin()
# 判断是否是验证提交
if form.validate_on_submit():
username = form.name.data
if username == 'admin':
return redirect(url_for('success'))
else:
# 渲染表单
flash(u'用户名不正确')
# 渲染
return render_template('mtflogin.html', form=form)

模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{%extends 'bootstrap/base.html'%}
{%import 'bootstrap/wtf.html' as wtf%}
{%block content%}
<div class="container col-lg-3 col-lg-offset-3">
<div class="page-header">
<h3>WTF-Login</h3>
</div>
<div>
{%for message in get_flashed_messages()%}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">&times;</button>
{{message}}
</div>
{%endfor%}
{{wtf.quick_form(form)}}
</div>
</div>
{%endblock%}

效果:

img

0x05会话保持

利用session机制完成会话保持:

1
2
3
4
5
6
7
8
9
10
11
12
13
@app.route('/baselogin2', methods=('POST', 'GET'))
def baselogin2():
form = BaseLogin()
# 判断是否是验证提交
if form.validate_on_submit():
name=session.get('name')
if name is None and name!=form.name.data:
flash(u'您已经切换了用户')
return redirect(url_for('success'))
elif name==None:
session['name']=form.name.data
# 渲染
return render_template('mtflogin.html', form=form)

0x06自定义验证器

开始做一个可复用的验证器,简单的namefield验证器

1
2
3
4
5
6
class MyForm(Form):
name = StringField('Name', [InputRequired()])

def validate_name(form, field):
if len(field.data) > 50:
raise ValidationError('Name must be less than 50 characters')

我们可以将函数放在类中,也可以将函数放在类外任何可以调用到函数的地方,如下:

1
2
3
4
5
6
def my_length_check(form, field):
if len(field.data) > 50:
raise ValidationError('Field must be less than 50 characters')

class MyForm(Form):
name = StringField('Name', [InputRequired(), my_length_check])

我们也可以通过创建一个工厂来让验证器更强大

1
2
3
4
5
6
7
8
9
10
def length(min=-1, max=-1):
message = 'Must be between %d and %d characters long.' % (min, max)
def _length(form, field):
l = field.data and len(field.data) or 0
if l < min or max != -1 and l > max:
raise ValidationError(message)
return _length

class MyForm(Form):
name = StringField('Name', [InputRequired(), length(max=50)])

现在我们创建了一个可控制长度的字符串长度验证器,下面我们将验证器做的可复用性更高,并且让用户可以自定义自己的错误消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Length(object):

def __init__(self, min=-1, max=-1, message=None):
self.min = min
self.max = max
if not message:
message = u'Field must be between %i and %icharacters long.' % (min, max)
self.message = message
def __call__(self, form, field):
l = field.data and len(field.data) or 0
if l < self.min or self.max != -1 and l > self.max:
raise ValidationError(self.message)

length = Length

https://blog.csdn.net/qq_35562816/article/details/80002315