flask-wtf使用笔记

一、WTForms

这个库一般有两个作用:

  1. 做表单验证
  2. 模版渲染

表单验证

  1. 自定义一个表单类,继承 wtforms.Form;
  2. 定义好需要验证的字段,字段的名字必须和表单中需要验证的字段名的 name 保持一致;
  3. 在需要验证的字段上指定好具体的字段类型,
  4. 在相关的字段上指定好验证器。
  5. 以后只需要使用这个表单类的对象,并且把 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代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册页面</title>
</head>
<body>
    <h2>用户注册</h2>
    <form action="" method="post">
        <table>
            <tr>
                <td>
                    <span>用户名:</span>
                </td>
                <td>
                    <input type="text" name="username"><br>
                </td>
            </tr>
            <tr>
                <td>
                    <span>密码:</span>
                </td>
                <td>
                    <input type="password" name="password" >
                </td>
            </tr
            <tr>
                <td>
                    <span>重复密码:</span>
                </td>
                <td>
                    <input type="password" name="re_password" >
                </td>
            </tr>
            <tr>
                <td></td>
                <td>
                    <input type="submit" value="提交">
                </td>
            </tr>
        </table>
    </form>
</body>
</html>

常用验证器

  • Email: 邮箱验证。

  • EqualTo: 验证和其他字段是否一致,常用做确认密码验证。

  • InputRequired:原始数据需要验证。

  • Length: 长度限制。

  • NumberRange: 数字区间,有 min 和 max 两个值进行限制。

  • Regexp: 只定义正则表达式。

  • URL: 必须是 URL 的形式。

  • UUID: 验证 UUID。

自定义验证字段

如果需要对表单中的字段做更细化的验证,可以使用自定义验证器来验证,步骤如下:

  1. 定义一个方法,方法的名字规则是: validate_字段名(self,filed)
  2. 在方法中使用field.data可以获取到这个字段的具体值。
  3. 如果条件满足,可以什么都不做,如果验证失败,那么应该抛出一个wtforms.validators.ValidationError(message)异常,其中 message是验证异常的消息提示。
    示例代码如下:
captcha = StringField(validators=[Length(4, 4)])

    # 自定义验证字段
    def validate_captcha(self, field):
        # 此处模拟验证码是 1234
        if field.data != '1234':
            raise ValidationError('输入的验证码不正确')

使用 WTForms 渲染模版

步骤:

  1. 示例化表单类,传递到模版文件中,例如 form = LoginForm()
  2. 在模版中使用 {{模版对象.字段名}}生成模版,可以通过 模版对象.字段名.label获取设置好显示的字段名,在定义每个字段的时候,可以传递label=xxx,可以在渲染模版的时候用做字段名

二、flask-wtf 笔记

Flask-WTF 是简化了 WTForms 操作的一个第三方库。WRFroms表单的两个主要功能是验证用户提交数据的合法性以及渲染模版。当然还包括一些其他功能。CSRF保护,文件上传等。
安装Flask-WTF 默认也会安装 WTForms,安装命令如下:

pip install flask-wtf

1.表单验证(同wtforms,增加了部分验证器)

2.模版渲染(使用方式同上)


文件上传笔记

  1. 在文件上传的表单中,需要指定 enctype="multipart/form-data" 才能上传文件

  2. 在后台如果需要获取到文件,需使用 request.files.get('xxx') 来获取文件,xxx是指定文件的 name 属性。

  3. 保存文件之前,先使用 werkzeug.utils.secute_filename 来对上传的文件名做过滤。这样才能保证不会有安全问题。

  4. 可以调用 文件.sava()方法保存文件,需要传递 保存文件的文件名(路径 + 文件名)。

  5. 从服务器上读取一个文件,应该定义一个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/<filename>')
def get_image(filename):
    return send_from_directory(UPLOAD_DIR, filename)  # 此处的UPLOAD_DIR为上传文件的目录字符串

使用flask-wtf验证上传的文件

步骤:

  1. 定义表单的时候,对文件的字段,需要采用FileField这个类型。
  2. 验证器应该从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='您没有输入描述')])
    
  3. 在视图文件中,使用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

  1. 设置cookie
    设置cookie应该在Response的对象上设置,通过flask.Response对象的set_cookie方法
  1. 删除cookie
    通过flask.Response对象的delete_cookie方法

  2. 设置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
  1. 设置cookie有效域名
    cookie默认是只能在主域名下使用,如果要在子域名下使用cookie,需要在设置cookie的时候,指定domain参数,这样子域名才能使用cookie信息。例如:
resp.set_cookie(key='username', value='ck', domain='.ck.com')

2. session

  1. session 的概念
    session:session和cookie的作用类似,都是为了存储用户相关的信息,不同的是,cookie是存储在本地浏览器,session是一个思路/概念,一个服务器存储授权信息的解决方案,不同的服务器,不同的框架,不同的语言有不同的实现,虽然实现不一样,但是他们的目的都是服务器为了方便存储数据的。session的出现,是为了解决cookie存储数据不安全。

  2. cookie和session结合使用

  • 存储在服务端: 通过cookie存储一个session_id,然后具体的数据则是保存在session中。
  • 将session数据加密,然后存储在cookie中。

Flask操作session

  1. 设置session:
    设置session在session对象上进行设置的,通过"flask.session"对象上的一个"session.setdefault()"方法或者字典来进行设置session信息
# one
session.setdefault("name","angle")
# two
session["name"] = "angle"
  1. 获取session:
    通过"session.get()"方法获取
name = session.get("name")
  1. 删除指定session:
    通过“session.pop()”方法删除指定的session值
# 1.删除指定session
session.pop("name")
# 2. 删除指定 session
del session['name']
  1. 清除所有的session值
# 清除所有session
session.clear()
  1. 设置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 都相同,才认为这个请求是正常的,否则就是伪造的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容