一、WTForms
这个库一般有两个作用:
- 做表单验证
- 模版渲染
表单验证
- 自定义一个表单类,继承
wtforms.Form
; - 定义好需要验证的字段,字段的名字必须和表单中需要验证的字段名的 name 保持一致;
- 在需要验证的字段上指定好具体的字段类型,
- 在相关的字段上指定好验证器。
- 以后只需要使用这个表单类的对象,并且把
request.for
传递给这个表单类,调用对象.validate()
如果返回 True,表示用户输入合法,
如果验证错误,可以通过表单类对象.errors
来获取错误信息
示例代码如下:
项目代码:
# app.py
from flask import Flask, render_template, request
from wtforms import Form, StringField
from wtforms.validators import Length, EqualTo
app = Flask(__name__)
class RegisterForm(Form):
username = StringField(validators=[Length(min=3, max=10,message='用户名长度应在3-10个字符之间')])
password = StringField(validators=[Length(min=6, max=10,message='密码应该在 6-10个字符之间')])
re_password = StringField(validators=[Length(min=6, max=10,message='重复密码应该在 6-10个字符之间'), EqualTo('password',message='重复密码和密码不一致!')])
@app.route('/register/', methods=['GET', 'POST'])
def register():
if request.method == 'GET':
return render_template('register.html')
if request.method == 'POST':
# 用 WTForm 做表单验证
form = RegisterForm(request.form)
if form.validate():
return 'success'
else:
print(form.errors)
return form.errors
html代码:
注册页面
用户注册
常用验证器
Email
: 邮箱验证。EqualTo
: 验证和其他字段是否一致,常用做确认密码验证。InputRequired
:原始数据需要验证。Length
: 长度限制。NumberRange
: 数字区间,有 min 和 max 两个值进行限制。Regexp
: 只定义正则表达式。URL
: 必须是 URL 的形式。UUID
: 验证 UUID。
自定义验证字段
如果需要对表单中的字段做更细化的验证,可以使用自定义验证器来验证,步骤如下:
- 定义一个方法,方法的名字规则是:
validate_字段名(self,filed)
。 - 在方法中使用
field.data
可以获取到这个字段的具体值。 - 如果条件满足,可以什么都不做,如果验证失败,那么应该抛出一个
wtforms.validators.ValidationError(message)
异常,其中message
是验证异常的消息提示。
示例代码如下:
captcha = StringField(validators=[Length(4, 4)])
# 自定义验证字段
def validate_captcha(self, field):
# 此处模拟验证码是 1234
if field.data != '1234':
raise ValidationError('输入的验证码不正确')
使用 WTForms 渲染模版
步骤:
- 示例化表单类,传递到模版文件中,例如
form = LoginForm()
- 在模版中使用
{{模版对象.字段名}}
生成模版,可以通过模版对象.字段名.label
获取设置好显示的字段名,在定义每个字段的时候,可以传递label=xxx
,可以在渲染模版的时候用做字段名
二、flask-wtf 笔记
Flask-WTF 是简化了 WTForms 操作的一个第三方库。WRFroms表单的两个主要功能是验证用户提交数据的合法性以及渲染模版。当然还包括一些其他功能。CSRF保护,文件上传等。
安装Flask-WTF 默认也会安装 WTForms,安装命令如下:
pip install flask-wtf
1.表单验证(同wtforms,增加了部分验证器)
2.模版渲染(使用方式同上)
文件上传笔记
在文件上传的表单中,需要指定
enctype="multipart/form-data"
才能上传文件在后台如果需要获取到文件,需使用
request.files.get('xxx')
来获取文件,xxx
是指定文件的 name 属性。保存文件之前,先使用
werkzeug.utils.secute_filename
来对上传的文件名做过滤。这样才能保证不会有安全问题。可以调用
文件.sava()
方法保存文件,需要传递 保存文件的文件名(路径 + 文件名)。从服务器上读取一个文件,应该定义一个url与视图函数,来获取指定文件。在这个视图函数中,使用
send_form_directory(文件的目录,文件名)
来获取指定文件。
示例代码如下:
@app.route('/upload', methods=['GET', 'POST'])
def upload():
if request.method == 'GET':
return render_template('upload.html')
else:
# 获取描述信息
desc = request.form.get('desc')
# 获取文件
avatar = request.files.get('avatar')
# 可以通过 获取到的文件的 save方法保存文件,filename 属性可以获取到文件的文件名
# 为了安全起见,包装文件名
filename = secure_filename(avatar.filename)
avatar.save(os.path.join(UPLOAD_DIR, filename))
return '上传成功'
@app.route('/images/')
def get_image(filename):
return send_from_directory(UPLOAD_DIR, filename) # 此处的UPLOAD_DIR为上传文件的目录字符串
使用flask-wtf验证上传的文件
步骤:
- 定义表单的时候,对文件的字段,需要采用
FileField
这个类型。 - 验证器应该从
flask_tf.file
中导入.-
FileRequired
验证文件是否为空 -
FileAllowed
指定文件格式
例如:
class UploadForm(Form): avatar = FileField( label='头像', validators=[ FileRequired(message='您没有选择文件'), FileAllowed(upload_set=['jpg', 'png', 'gif', 'jpeg'], message='您上传的文件不符合规范') ] ) desc = StringField(label='描述', validators=[InputRequired(message='您没有输入描述')])
-
- 在视图文件中,使用
werkzeug.datastructures.CombinedMultiDict()
方法
把表单和文件结合在一起,然后初始化表单对象。
示例如下:
# 利用 flask-wtf 验证文件上传
@app.route('/upload', methods=['GET', 'POST'])
def upload():
if request.method == 'GET':
upload_form = UploadForm()
return render_template('upload_with_flask-wtf.html', form=upload_form)
else:
form = UploadForm(CombinedMultiDict([request.form, request.files])) # 把表单和上传的文件共同组合为 form
if form.validate():
# 获取描述信息
desc = request.form.get('desc')
# 获取文件
avatar = request.files.get('avatar')
# 可以通过 获取到的文件的 save方法保存文件,filename 属性可以获取到文件的文件名
# 为了安全起见,包装文件名
filename = secure_filename(avatar.filename)
avatar.save(os.path.join(UPLOAD_DIR, filename))
return '上传成功'
else:
return form.errors
预防CSRF攻击
基础知识
1. cookie
- 解决http无状态的问题
- 存储大小有限 一般不超过4K
- 存储在本地浏览器
- 有有效期,服务器设置,浏览器清除
- 有域名的概念
flask操作cookie
- 设置cookie
设置cookie应该在Response的对象上设置,通过flask.Response
对象的set_cookie
方法
删除cookie
通过flask.Response
对象的delete_cookie
方法设置cookie有效期
在调用Response.set_cookie()
的时候,可以通过指定参数来设置 cookie 的有效期
-
max_age
参数:指定一个距离现在时间的秒数,在指定的时候后,cookie到期;注意:在ie8以下的浏览器不支持这个 -
expires
参数:datatime.datatime
类型的时间,注意:这个时间是格林尼治时间,比北京时间晚8个小时
如果没有设置这两个参数都没有设置,cookie会在会话结束(关闭浏览器后)后到期
示例代码:
@app.route('/')
def index():
resp = Response('主页')
# 设置 cookie
# expires = datetime(2019, 10, 28, 0, 0, 0)
# resp.set_cookie(key='username', value='ck', expires=expires,max_age=60)
resp.set_cookie(key='username', value='ck')
return resp
- 设置cookie有效域名
cookie默认是只能在主域名下使用,如果要在子域名下使用cookie,需要在设置cookie的时候,指定domain
参数,这样子域名才能使用cookie信息。例如:
resp.set_cookie(key='username', value='ck', domain='.ck.com')
2. session
session 的概念
session:session和cookie的作用类似,都是为了存储用户相关的信息,不同的是,cookie是存储在本地浏览器,session是一个思路/概念,一个服务器存储授权信息的解决方案,不同的服务器,不同的框架,不同的语言有不同的实现,虽然实现不一样,但是他们的目的都是服务器为了方便存储数据的。session的出现,是为了解决cookie存储数据不安全。cookie和session结合使用
- 存储在服务端: 通过cookie存储一个session_id,然后具体的数据则是保存在session中。
- 将session数据加密,然后存储在cookie中。
Flask操作session
- 设置session:
设置session在session对象上进行设置的,通过"flask.session"对象上的一个"session.setdefault()"方法或者字典来进行设置session信息
# one
session.setdefault("name","angle")
# two
session["name"] = "angle"
- 获取session:
通过"session.get()"方法获取
name = session.get("name")
- 删除指定session:
通过“session.pop()”方法删除指定的session值
# 1.删除指定session
session.pop("name")
# 2. 删除指定 session
del session['name']
- 清除所有的session值
# 清除所有session
session.clear()
- 设置session有效期:
如果没有配置session的有效期,默认是浏览器关闭之后,结束session,
通过配置config进行配置,默认有效期为31天
from datetime import timedelta
# 设置会话有效期时间:两个小时以后会话有效期时间过期
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=2)
# 设置session的过期时间,permanent:持久性,默认时间为一个月
session.permanent = True
**注意:**使用session需要配置SECRET_KEY
```python
import os
# os.unrandom(n):产生24位的随机数
app.config["SECRET_KEY"] = os.urandom(24)
```
CSRF 攻击与防御
csrf攻击概述:
CSRF(Cross Site Request Forgery,跨站请求伪造)是一种网络的攻击方式。
csrf攻击原理:
网站是通过cookie来实现登录功能的。而cookie只要存在浏览器中,那么浏览器在访问这个cookie的服务器的时候,就会自动的携带cookie信息到服务器上去。那么这时候就存在一个漏洞了,如果你访问了一个别有用心或病毒网站,这个网站可以在网页源代码中插入js代码,使用js代码给其他服务器发送请求(比如ICBC的转账请求)。那么因为在发送请求的时候,浏览器会自动的把cookie发送给对应的服务器,这时候响应的服务器(比如ICBC网站),就不知道这个请求时是伪造的,就被欺骗过去了,从而达到在用户不知情的情况下,给某个服务器发送了一个请求(比如转账)。
防御csrf攻击
csrf攻击的要点就是在向服务器发送请求的时候,相应的 cookie会自动发送给对应的服务器。造成服务器不知道这个请求是用户发起的还是伪造的。这时候,我们可以在用户媒体访问有表单页面的时候,在网页源代码中加入一个随机字符串叫做 csrf_token
,在 cookie 中同样也加入一个相同的 csrf_token 字符串。以后在给服务器发送请求的时候,必须在 body 中以及 cookie 中都携带了 csrf_token,服务器只有监测到 cookie 中的 csrf_token 和 body 中的 token 都相同,才认为这个请求是正常的,否则就是伪造的。