第六章 Flask-WTF(二)

Flask-WTF

Flask-WTF是简化了WTForms操作的一个第三方库。

WTForms表单的两个主要功能是验证用户提交数据的合法性以及渲染模板。

当然还包括一些其他的功能:CSRF保护,文件上传等。安装Flask-WTF默认也会安装WTForms,安装Flask-WTF如下:

回顾表单:

input> 标签

🔹<input> 标签根据不同的 type 属性,可以变化为多种形态。

<form action='url'method='get/post'>
    <input type='text'/>
    <input type='password'/>
    <input type='radio'/>
    <input type='checkbox'/>
    <textarea></textarea>
<input type='submit'/>
</form>

点击提交(type='submit')时,form表单会自动把name属性值作为键名,value属性值作为键值,组成键值对形式,

然后form表单会按指定的method方式吧数据发送到指定的ur1路径去。

我们大体介绍一下:input的type属性

radio:

Radio 对象:代表 HTML 表单中的单选按钮。

在 HTML 表单中 <input type="radio"> 每出现一次,一个 Radio 对象就会被创建。

单选按钮是表示一组互斥选项按钮中的一个。当一个按钮被选中,之前选中的按钮就变为非选中的。

当单选按钮被选中或不选中时,该按钮就会触发 onclick 事件句柄。

password :

Password 对象代表 HTML 表单中的密码字段。

HTML 的 <input type="password"> 标签在表单上每出现一次,一个 Password 对象就会被创建。

该文本输入字段供用户输入某些敏感的数据,比如密码等。当用户输入的时候,他的输入是被掩盖的(例如使用星号*),以防止旁边的人从他背后看到输入的内容。不过需要注意的是,当表单提交时,输入是用明文发送的。

与类型为 "text" 的元素类似,当用户改变显示值时,它会触发 onchange 事件句柄。

Text :

Text 对象代表 HTML 表单中的文本输入域。

在 HTML 表单中 <input type="text"> 每出现一次,Text 对象就会被创建。

该元素可创建一个单行的文本输入字段。当用户编辑显示的文本并随后把输入焦点转移到其他元素的时候,会触发 onchange 事件句柄。

button: 

Button 对象代表 HTML 文档中的一个按钮。我认为是一种多选按钮

该元素没有默认的行为,但是必须有一个 onclick 事件句柄以便使用。

在 HTML 文档中 <input type="button"> 标签每出现一次,一个 Button 对象 就会被创建。

注意

1.radio与button的差异

    <label>
    性别: 男<input type="radio" name="a" value="0"><input type="radio" name="a" value="1">
    </label>
        <label>
    年龄: 成年<input type="radio" name="b" value="0"> 未成年<input type="radio" name="b" value="1">
    </label>

在radio中,从属相同的name具有排他性,从属不同的name独立性。从属不同的form表单也具有独立性。

<form>
        是否本科: 是<input type="radio" name="a" value="0"> 不是<input type="radio" name="a" value="1">
    </label>
</form>

 

而button适用于多选问题

2.<label>

    <label>姓名
        <input name="text1" type='text'/>
    </label>

一般都会被label包裹起来

 

3.post与get请求

method提交方式:

get:

数据会显示在地址栏,显示的形式是ur1?数据数据是键值对形式存在,多个键值对之间使用&符号连接,?号最多出现一次,&可以出现多次。

post:

数据不在地址栏中显示,可以在network监听工具中监听,在请求头中数据中不会出现?和&符号,正常倩况下是看不到这个数据的。

<form action='/' method='get或者post'>
    <label>姓名
        <input name="text1" type='text'/>
    </label>
    <br>
    <label>密码
        <input name="pass" type='password' />
    </label>
    <br>
    <label>
    性别: 男<input type="radio" name="a" value="0"><input type="radio" name="a" value="1">
    </label>
        <label>
    年龄: 成年<input type="radio" name="b" value="0"> 未成年<input type="radio" name="b" value="1">
    </label>
    <br>
    <label>
        <input name="checkboxx" type='checkbox'/>
    </label>

    <input type='submit'/>
</form>
get或者post

 

 

二、<select>标签(下拉列表)

 🔹<select> 标签定义下拉列表:

<select name="cars">
    <option value="volvo">Volvo</option>
    <option value="saab">Saab</option>
    <option value="fiat">Fiat</option>
    <option value="audi">Audi</option>
</select>

三、<option> 是select的元素

🔹<option> 是select标签的元素定义待选择的选项。列表通常会把首个选项显示为被选选项。您能够通过添加 selected 属性来定义预定义选项。

<select name="cars">
    <option value="volvo">Volvo</option>
    <option value="saab">Saab</option>
    <option value="fiat">Fiat</option>
    <option value="audi">Audi</option>
    <option value="fiat"selected>Fiat</option><br>
</select>

 

四、<textarea> 标签

🔹<textarea> 元素定义多行输入字段(文本域)

<textarea>1123131</textarea>

 

 

五、<button> 元素

🔹<button> 元素定义可点击的按钮

<button type="button" onclick="alert('Hello world!')">click Me!</button>

 

 

 

六、HTML5 表单元素   

🔹HTML5 增加了如下表单元素:

🔹<datalist>

🔹<keygen>

🔹<output>

🔹注释:默认地,浏览器不会显示未知元素。新元素不会破坏您的页面。

七、HTML5 <datalist> 元素

🔹<datalist> 元素为 <input> 元素规定预定义选项列表。用户会在他们输入数据时看到预定义选项的下拉列表。<input> 元素的 list 属性必须引用 <datalist> 元素的 id 属性。

 

 

 

 

做表单验证

1. 自定义一个表单类,继承自wtforms.Form类。
2. 定义好需要验证的字段,字段的名字必须和模版中那些需要验证的input标签的name属性值保持一致。
3. 在需要验证的字段上,需要指定好具体的数据类型。
4. 在相关的字段上,指定验证器。
5. 以后在视图中,就只需要使用这个表单类的对象,并且把需要验证的数据,也就是request.form传给这个表单类,以后调用form.validate()方法,如果返回True,那么代表用户输入的数据都是合法的,否则代表用户输入的数据是有问题的。如果验证失败了,那么可以通过form.errors来获取具体的错误信息。

ReistForm类的代码:

from wtforms import Form,StringField,IntegerField,BooleanField,DateField,SelectField
from wtforms.validators import Length,EqualTo,Email,InputRequired,NumberRange,Regexp,URL,UUID,ValidationError

class RegistForm(Form):
    username = StringField(validators=[Length(min=3,max=10,message='用户名长度必须在3到10位之间')])
    password = StringField(validators=[Length(min=6,max=10)])
    password_repeat = StringField(validators=        [Length(min=6,max=10),EqualTo("password")])

 

 

@app.route('/login/',methods=['GET','POST'])
def login():
    if request.method == 'GET':
        return render_template("login.html")
    else:
        form = RegistForm(request.form)
        if form.validate():
            return "success"
        else:
            print(form.errors)
        return "fail"

 

常用的验证器:

数据发送过来,经过表单验证,因此需要验证器来进行验证,以下对一些常用的内置验证器进行讲解:

 

  • Email:验证上传的数据是否为邮箱。
  • EqualTo:验证上传的数据是否和另外一个字段相等,常用的就是密码和确认密码两个字段是否相等。
  • InputRequired:原始数据的需要验证。如果不是特殊情况,应该使用InputRequired。
  • Length:长皮限制,有min和max两个值进行限制。
  • NumberRange:数字的区间,有min和max两个值限制,如果处在这两个数字之间则满足。
  • Regexp:自定义正则表达式。
  • URL:必须要是URL的形式。
  • UUID:验证UUID。

 

cookie与session

1.cookie概念:

  在网站中,http请求是无状态的。即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不能知道当前请求是哪个用户。

cookie 的出现就是为了解决这个问题,第一次登录后服务器返回一些数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求的时候,

就会自动的把上次请求存储的cookie数据自动的携带给服务器,服务器通过浏览器携带的数据就能判断当前用户是哪个了。cookie存储的数据量有限,不同的浏览器有不同的存储大小,但一般不超过4KB。因此使用cookie 只能存储一些小量的数据。

2.session概念:

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


3.cookie和session结合使用:

web开发发展至今,cookie和session的使用已经出现了一些非常成熟的方案。

在如今的市场或者企业里,一般有两种存储方式:

存储在服务端:

  通过cookie存储一个session_id,然后具体的数据则是保存在session中,服务器端可以采用mysq1、redis、memcached等来存储。

如果用户已经登录,则服务器会在cookie中保存一个session_id,下次再次请求的时候,会把该 session_id携带上来,服务器根据session_id在session 库中获取用户的session数据。

就能知道该用户到底是谁,以及之前保存的一些状态信息。这种专业术语叫做 server side session。

       存储在服务器的数据会更加的安全,不容易被窃取。但存储在服务器也有一定的弊端,就是会占用服务器的资源,但现在服务器已经发展至今,一些session信息还

是绰绰有余的。

将session数据加密,然后存储在cookie中。这种专业术语叫做client side session。flask采用的就是这种方式,但是也可以替换成其他形式。

存储在客户端:

客户端发送验证信息过来(比如用户名和密码)。服务器把相关的验证信息进行一个非常严格和安全的加密方式进行加密,然后再把这个加密后的信息存储到cookie,返回给浏览器,浏览器保存到本地。

以后浏览器再请求服务器的时候,就会自动的把cookie发送给服务器,服务器拿到cookie后,就从cookie找到加密的那个session信息,然后也可以实现安全识别用户的需求了。

 

1. 设置session:通过flask.session就可以操作session,session类型为<class 'werkzeug.local.LocalProxy'>,操作session与操作字典是一样。session['username']='wqbin'。
2. 获取session:session.get(key)。
3. 删除session中的值:类似字典。可以有三种方式删除session中的值。

  • session.pop(key)。
  • del session[key]。
  • session.clear():删除session中所有的值。

4. 设置session的有效期:

  1. 如果没有设置session的有效期。那么默认就是浏览器关闭后过期。
  2. 如果设置session.permanent=True,那么就会默认在31天后过期。
  3. 可以自定义设置app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hour=2)在两个小时后过期。

 

案例

app = Flask(__name__)
#生成随机字符串
app.config['SECRET_KEY'] = os.urandom(30)

@app.route('/')
def index():
    session['username'] = 'wqbin'
    session['user_id'] = '1024'
    # permanent:持久化 默认31天
    session.permanent = True
    print(type(session))
    return 'Hello World!'

@app.route('/get_session/')
def get_session():
    username = session.get('username')
    user_id = session.get('user_id')
    print(user_id)
    return username or '没有session'

@app.route('/delete_session/')
def delete_session():
    # session.pop('username')
    session.clear()
    return '删除成功'


if __name__ == '__main__':
    app.run(debug=True)

 

CSRF攻击

CSRF攻击概述:

CSRF(Cross Site Request Forgery,跨站域请求伪造)是一种网络的攻击方式,它在2007年曾被列为互联网20大安全隐患之一。
其他安全隐患,比如SOL脚本注入,跨站域脚本攻击等在近年来已经逐渐为众人熟知,很多网站也都针对他们进行了防御。
然而,对于大多数人来说,CSRF却依然是一个陌生的概念。即便是大名鼎鼎的Gmail,在2007年底也存在看CSRF漏洞,从而被黑客攻击而使Gmail 的用户造成巨大的损失。

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中的csrf_token 都相同,才认为这个请求是正常的,否则就是伪造的。那么黑客就没办法伪造请求了。

 

csrf=CsrfProtect()
csrf.init_app(app)

 或者是针对某一个视图函数,使用csrf.protect装饰器来开启csrf保护功能。并且如果已经开启了全局的srf保护,想要关闭某个视图函数的csrf保护功能,可以使用csrf.exempt装饰器来取消本视图函数的保护功能。

AJAX的CSRF保护:
在AJAX中要使用csrf保护,必须手动添加x-CSRFToken到Header中。但是CSRF从哪里来,还是需要通过模板给渲染,而Flask 比较推荐的方式是在aeta标签中渲染csrf,如下:

<meta name="csrf-token" content="{{ csrf_token()}}">

如果要发送AJAX请求,则在发送之前要添加CS RF,代码如下(使用了jQuery):

var csrftoken=S('meta[ name=csrf-token]').attr(' content')
$.ajaxSetup({
    beforeSend: function(xhr, settings){
        if(!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain){
            xhr. setRequestHeader("X-CSRFToken", csrftoken)
    })

 

--未完待续

posted @ 2019-12-02 23:11  wqbin  阅读(742)  评论(0)    收藏  举报