使用Python的Flask框架表單插件Flask-WTF實現(xiàn)Web登錄驗證
表單是讓用戶與我們的網(wǎng)頁應用程序交互的基本元素。Flask 本身并不會幫助我們處理表單,但是 Flask-WTF 擴展讓我們在我們的 Flask 應用程序中使用流行的 WTForms 包。這個包使得定義表單和處理提交容易一些。
Flask-WTF
我們想要使用 Flask-WTF 做的第一件事情(在安裝它以后,GitHub項目頁:https://github.com/lepture/flask-wtf )就是在 myapp.forms 包中定義一個表單。
# ourapp/forms.py from flask_wtf import Form from wtforms import StringField, PasswordField from wtforms.validators import DataRequired, Email class EmailPasswordForm(Form): email = StringField('Email', validators=[DataRequired(), Email()]) password = PasswordField('Password', validators=[DataRequired()])
在 Flask-WTF 0.9 版本以前,F(xiàn)lask-WTF 提供了針對 WTForms 字段以及驗證器的自己的封裝。你可能看到外面一大堆的代碼是從 flask.ext.wtforms 中不是從 wtforms 中導入 TextField,PasswordField。
在 Flask-WTF 0.9 版本以后,我們應該直接從 wtforms 中導入這些字段和驗證器。
我們定義的表單是一個用戶登錄表單。我們把它叫做 EmailPasswordForm(),我們可以重用這個同樣的表單類(Form)去做其它的一些事情,像注冊表單。這里我們沒有去定義一個又長又沒有用的表單,而是選擇一個很常用的表單,只是為了給你們介紹使用 Flask-WTF 定義表單的方式。也許以后在正式項目中會定義一個特別復雜表單。對于表單中包含字段名稱,我們建議使用一個清楚的名稱,并且在一個表單中保持唯一。不得不說,對于一個長的表單,我們可能要給出一個更符合上文的字段名稱。
登錄表單可以替我們做一些事情。它能夠保證我們應用程序的安全以防止 CSRF 漏洞,驗證用戶輸入并且渲染適當?shù)臉擞?,這些標記是我們?yōu)楸韱味x的字段。
CSRF 保護和驗證
CSRF 表示跨站請求偽造。CSRF 攻擊是指第三方偽造(像一個表單提交)請求到一個應用程序的服務器。一個易受攻擊的服務器假設(shè)從一個表單來的數(shù)據(jù)是來自它自己的網(wǎng)站并且采取相應的操作。
作為一個例子,比方說,一個郵件提供商可以讓你通過提交一個表單來刪除你的賬號。表單發(fā)送一個 POST 請求到服務器上的 account_delete 端點并且當表單被提交的時候刪除登錄的賬號。我們可以在自己的網(wǎng)站上創(chuàng)建一個表單,該表單發(fā)送一個 POST 請求到同一個 account_delete 端點。現(xiàn)在,如果我們讓某人點擊我們表單的提交按鈕(或者通過 JavaScript 來這樣做),郵件提供商提供的登錄賬號就會被刪除掉。當然郵件提供商還不知道表單提交并不是發(fā)生在他們的網(wǎng)站上。
因此如何才能阻止 POST 請求來自別的網(wǎng)站?WTForms 通過在渲染每一個表單的時候生成一個唯一的令牌使得成為可能。生成的令牌會被傳回到服務器,伴隨著 POST 請求的數(shù)據(jù),在表單被接受之前令牌必須接受服務器的驗證。關(guān)鍵的是令牌是與存儲在用戶會話(cookies)的一個值有關(guān)并且會在一段時間后失效(默認是 30 分鐘)。這種方式就能夠保證提交一個有效表單的人就是加載頁面的人(或者至少是使用同一電腦的人),而且他們只能在加載頁面 30 分鐘內(nèi)這樣做。
要開始使用 Flask-WTF 保護 CSRF,我們需要為我們的登錄頁定義一個視圖。
# ourapp/views.py from flask import render_template, redirect, url_for from . import app from .forms import EmailPasswordForm @app.route('/login', methods=["GET", "POST"]) def login(): form = EmailPasswordForm() if form.validate_on_submit(): # Check the password and log the user in # [...] return redirect(url_for('index')) return render_template('login.html', form=form)
如果表單已經(jīng)被提交和驗證的話,我們可以繼續(xù)登錄的邏輯。如果它沒有被提交的話(例如,只是一個 GET 請求),我們就要把表單對象傳遞給我們的模板,以便它能夠被渲染。下面就是我們使用 CSRF 保護的時候模板的樣子。
{# ourapp/templates/login.html #} {% extends "layout.html" %} {% endraw %} <html> <head> <title>Login Page</title> </head> <body> <form action="{{ url_for('login') }}" method="post"> <input type="text" name="email" /> <input type="password" name="password" /> {{ form.csrf_token }} </form> </body> </html>
{% raw %}{{ form.csrf_token }}{% endraw %} 渲染了一個隱藏的字段,該字段包含那些奇特的 CSRF 令牌,并且當 WTForms 驗證表單的時候會尋找這個字段。我們不用擔心包含處理令牌的邏輯,WTForms 會主動幫我們?nèi)プ?。好哇?/p>
自定義驗證
除了由 WTForms 提供的內(nèi)置的表單驗證器(例如,Required(),Email() 等等),我們能創(chuàng)建我們自己的驗證器。我們將通過編寫一個 Unique() 驗證器來說明如何創(chuàng)建自己的驗證器,Unique() 驗證器是用來檢查數(shù)據(jù)庫并且確保用戶提供的值在數(shù)據(jù)庫中不存在。這能夠用于確保用戶名或者郵箱地址還沒有使用。沒有 WTForms 的話,我們可能要在視圖中做這些事情,但是現(xiàn)在我們可以在表單本身做些事情。
現(xiàn)在我們來定義一個簡單的注冊表單,其實這個表單和登錄的表單幾乎一樣。只是會在后面給它添加一些自定義的驗證器。
# ourapp/forms.py from flask_wtf import Form from wtforms import StringField, PasswordField from wtforms.validators import DataRequired, Email class EmailPasswordForm(Form): email = StringField('Email', validators=[DataRequired(), Email()]) password = PasswordField('Password', validators=[DataRequired()])
現(xiàn)在我們要添加我們的驗證器用來確保它們提供的郵箱地址不存在數(shù)據(jù)庫中。我們把這個驗證器放在一個新的 util 模塊,util.validators。
# ourapp/util/validators.py from wtforms.validators import ValidationError class Unique(object): def __init__(self, model, field, message=u'This element already exists.'): self.model = model self.field = field def __call__(self, form, field): check = self.model.query.filter(self.field == field.data).first() if check: raise ValidationError(self.message)
這個驗證器假設(shè)我們是使用 SQLAlchemy 來定義我們的模型。WTForms 期待驗證器返回某種可調(diào)用的對象(例如,一個可調(diào)用的類)。
在 Unique() 的 \_\_init\_\_ 中我們可以指定哪些參數(shù)傳入到驗證器中,在本例中我們要傳入相關(guān)的模型(例如,在我們例子中是傳入 User 模型)以及要檢查的字段。當驗證器被調(diào)用的時候,如果定義模型的任何實例匹配表單中提交的值,它將會拋出一個 ValidationError。我們也可以添加一個具有通用默認值的消息,它將會被包含在 ValidationError 中。
現(xiàn)在我們可以修改 EmailPasswordForm,使用我們自定義的 Unique 驗證器。
# ourapp/forms.py from flask_wtf import Form from wtforms import StringField, PasswordField from wtforms.validators import DataRequired from .util.validators import Unique from .models import User class EmailPasswordForm(Form): email = StringField('Email', validators=[DataRequired(), Email(), Unique( User, User.email, message='There is already an account with that email.')]) password = PasswordField('Password', validators=[DataRequired()])
渲染表單
WTForms 也能幫助我們?yōu)楸韱武秩境?HTML 表示。WTForms 實現(xiàn)的 Field 字段能夠渲染成該字段的 HTML 表示,所以為了渲染它們,我們只必須在我們模板中調(diào)用表單的字段。這就像渲染 csrf_token 字段。下面給出了一個登錄模板的示例,在里面我們使用 WTForms 來渲染我們的字段。
{# ourapp/templates/login.html #} {% extends "layout.html" %} <html> <head> <title>Login Page</title> </head> <body> <form action="" method="post"> {{ form.email }} {{ form.password }} {{ form.csrf_token }} </form> </body> </html>
我們可以自定義如何渲染字段,通過傳入字段的屬性作為參數(shù)到調(diào)用中。
<form action="" method="post"> {{ form.email.label }}: {{ form.email(placeholder='yourname@email.com') }} <br> {% raw %}{{ form.password.label }}: {{ form.password }}{% endraw %} <br> {% raw %}{{ form.csrf_token }}{% endraw %} </form>
處理 OpenID 登錄
現(xiàn)實生活中,我們發(fā)現(xiàn)有很多人都不知道他們擁有一些公共賬號。一部分大牌的網(wǎng)站或服務商都會為他們的會員提供公共賬號的認證。舉個栗子,如果你有一個 google 賬號,其實你就有了一個公共賬號,類似的還有 Yahoo, AOL, Flickr 等。
為了方便我們的用戶能簡單的使用他們的公共賬號,我們將把這些公共賬號的鏈接添加到一個列表,這樣用戶就不用自手工輸入了。
我們要把一些提供給用戶的公共賬號服務商定義到一個列表里面,這個列表就放到配置文件中吧 (fileconfig.py):
CSRF_ENABLED = True SECRET_KEY = 'you-will-never-guess' OPENID_PROVIDERS = [ { 'name': 'Google', 'url': 'https://www.google.com/accounts/o8/id' }, { 'name': 'Yahoo', 'url': 'https://me.yahoo.com' }, { 'name': 'AOL', 'url': 'http://openid.aol.com/<username>' }, { 'name': 'Flickr', 'url': 'http://www.flickr.com/<username>' }, { 'name': 'MyOpenID', 'url': 'https://www.myopenid.com' }]
接下來就是要在我們的登錄視圖函數(shù)中使用這個列表了:
@app.route('/login', methods = ['GET', 'POST']) def login(): form = LoginForm() if form.validate_on_submit(): flash('Login requested for OpenID="' + form.openid.data + '", remember_me=' + str(form.remember_me.data)) return redirect('/index') return render_template('login.html', title = 'Sign In', form = form, providers = app.config['OPENID_PROVIDERS'])
我們從 app.config 中引入了公共賬號服務商的配置列表,然后把它作為一個參數(shù)通過 render_template 函數(shù)引入到模板。
接下來要做的我想你也猜得到,我們需要在登錄模板中把這些服務商鏈接顯示出來。
<!-- extend base layout --> {% extends "base.html" %} {% block content %} <script type="text/javascript"> function set_openid(openid, pr) { u = openid.search('<username>') if (u != -1) { // openid requires username user = prompt('Enter your ' + pr + ' username:') openid = openid.substr(0, u) + user } form = document.forms['login']; form.elements['openid'].value = openid } </script> <h1>Sign In</h1> <form action="" method="post" name="login"> {{form.hidden_tag()}} <p> Please enter your OpenID, or select one of the providers below:<br> {{form.openid(size=80)}} {% for error in form.errors.openid %} <span style="color: red;">[{{error}}]</span> {% endfor %}<br> |{% for pr in providers %} <a href="javascript:set_openid('{{pr.url}}', '{{pr.name}}');">{{pr.name}}</a> | {% endfor %} </p> <p>{{form.remember_me}} Remember Me</p> <p><input type="submit" value="Sign In"></p> </form> {% endblock %}
這次的模板添加的東西似乎有點多。一些公共賬號需要提供用戶名,為了解決這個我們用了點 javascript。當用戶點擊相關(guān)的公共賬號鏈接時,需要用戶名的公共賬號會提示用戶輸入用戶名, javascript 會把用戶名處理成可用的公共賬號,最后再插入到 openid 字段的文本框中。
下面這個是在登錄頁面點擊 google 鏈接后顯示的截圖:
相關(guān)文章
Python+Opencv實現(xiàn)計算閉合區(qū)域面積
這篇文章主要介紹了利用Python?Opencv計算閉合區(qū)域的面積的原理以及實現(xiàn)代碼,文中的講解詳細易懂,感興趣的小伙伴快跟隨小編一起學習一下吧2022-03-03Numpy中關(guān)于arctan和arctan2的區(qū)別
這篇文章主要介紹了Numpy中關(guān)于arctan和arctan2的區(qū)別,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09python使用pandas讀取json文件并進行刷選導出xlsx文件的方法示例
這篇文章主要介紹了python使用pandas讀取json文件并進行刷選導出xlsx文件的方法,結(jié)合實例形式分析了python調(diào)用pandas模塊針對json數(shù)據(jù)操作的相關(guān)使用技巧,需要的朋友可以參考下2023-06-06Python利用matplotlib模塊數(shù)據(jù)可視化繪制3D圖
matplotlib是python最著名的繪圖庫,它提供了一整套和matlab相似的命令API,十分適合交互式地行制圖,下面這篇文章主要給大家介紹了關(guān)于Python利用matplotlib模塊數(shù)據(jù)可視化實現(xiàn)3D圖的相關(guān)資料,需要的朋友可以參考下2022-02-02Python實現(xiàn)從網(wǎng)絡(luò)攝像頭拉流的方法分享
這篇文章主要為大家詳細介紹了Python實現(xiàn)從網(wǎng)絡(luò)攝像頭拉流的幾種方法,文中的示例代碼講解詳細,具有一定的學習價值,感興趣的小伙伴可以了解一下2023-01-01