處理表單

表單是讓用戶與我們的網頁應用程序交互的基本元素腰奋。Flask 本身并不會幫助我們處理表單砌函,但是 Flask-WTF 擴展讓我們在我們的 Flask 應用程序中使用流行的 WTForms 包祭埂。這個包使得定義表單和處理提交容易一些茶敏。

Flask-WTF

我們想要使用 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 版本以前抖棘,Flask-WTF 提供了針對 WTForms 字段以及驗證器的自己的封裝。你可能看到外面一大堆的代碼是從 flask.ext.wtforms 中不是從 wtforms 中導入 TextField净捅,PasswordField疑枯。
  • 在 Flask-WTF 0.9 版本以后,我們應該直接從 wtforms 中導入這些字段和驗證器蛔六。

我們定義的表單是一個用戶登錄表單荆永。我們把它叫做 EmailPasswordForm(),我們可以重用這個同樣的表單類(Form)去做其它的一些事情国章,像注冊表單具钥。這里我們沒有去定義一個又長又沒有用的表單,而是選擇一個很常用的表單液兽,只是為了給你們介紹使用 Flask-WTF 定義表單的方式骂删。也許以后在正式項目中會定義一個特別復雜表單掌动。對于表單中包含字段名稱,我們建議使用一個清楚的名稱宁玫,并且在一個表單中保持唯一粗恢。不得不說,對于一個長的表單欧瘪,我們可能要給出一個更符合上文的字段名稱眷射。

登錄表單可以替我們做一些事情。它能夠保證我們應用程序的安全以防止 CSRF 漏洞佛掖,驗證用戶輸入并且渲染適當的標記妖碉,這些標記是我們?yōu)楸韱味x的字段。

CSRF 保護和驗證

CSRF 表示跨站請求偽造芥被。CSRF 攻擊是指第三方偽造(像一個表單提交)請求到一個應用程序的服務器欧宜。一個易受攻擊的服務器假設從一個表單來的數據是來自它自己的網站并且采取相應的操作。

作為一個例子拴魄,比方說冗茸,一個郵件提供商可以讓你通過提交一個表單來刪除你的賬號。表單發(fā)送一個 POST 請求到服務器上的 account_delete 端點并且當表單被提交的時候刪除登錄的賬號羹铅。我們可以在自己的網站上創(chuàng)建一個表單蚀狰,該表單發(fā)送一個 POST 請求到同一個 account_delete 端點。現在职员,如果我們讓某人點擊我們表單的提交按鈕(或者通過 JavaScript 來這樣做)麻蹋,郵件提供商提供的登錄賬號就會被刪除掉。當然郵件提供商還不知道表單提交并不是發(fā)生在他們的網站上焊切。

因此如何才能阻止 POST 請求來自別的網站扮授?WTForms 通過在渲染每一個表單的時候生成一個唯一的令牌使得成為可能。生成的令牌會被傳回到服務器专肪,伴隨著 POST 請求的數據刹勃,在表單被接受之前令牌必須接受服務器的驗證。關鍵的是令牌是與存儲在用戶會話(cookies)的一個值有關并且會在一段時間后失效(默認是 30 分鐘)嚎尤。這種方式就能夠保證提交一個有效表單的人就是加載頁面的人(或者至少是使用同一電腦的人)荔仁,而且他們只能在加載頁面 30 分鐘內這樣做。

  • 在文檔中 了解更多關于 WTForms 如何生成這些令牌芽死。
  • OWASP wiki 中了解 CSRF乏梁。

要開始使用 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)

如果表單已經被提交和驗證的話遇骑,我們可以繼續(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 會主動幫我們去做。好哇隶糕!

使用 CSRF 令牌保護 AJAX 調用

Flask-WTF CSRF 令牌不限于保護表單提交鹃祖。如果你的應用程序要處理其它可能會被偽造的請求(特別是 AJAX 調用)共耍,你也可以在那里添加 CSRF 保護!

自定義驗證

除了由 WTForms 提供的內置的表單驗證器(例如株旷,Required()再登,Email() 等等),我們能創(chuàng)建我們自己的驗證器晾剖。我們將通過編寫一個 Unique() 驗證器來說明如何創(chuàng)建自己的驗證器锉矢,Unique() 驗證器是用來檢查數據庫并且確保用戶提供的值在數據庫中不存在。這能夠用于確保用戶名或者郵箱地址還沒有使用齿尽。沒有 WTForms 的話沽损,我們可能要在視圖中做這些事情,但是現在我們可以在表單本身做些事情循头。

現在我們來定義一個簡單的注冊表單绵估,其實這個表單和登錄的表單幾乎一樣。只是會在后面給它添加一些自定義的驗證器卡骂。

# 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()])

現在我們要添加我們的驗證器用來確保它們提供的郵箱地址不存在數據庫中国裳。我們把這個驗證器放在一個新的 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)

這個驗證器假設我們是使用 SQLAlchemy 來定義我們的模型缝左。WTForms 期待驗證器返回某種可調用的對象(例如,一個可調用的類)浓若。

Unique()\\_\\_init\\_\\_ 中我們可以指定哪些參數傳入到驗證器中渺杉,在本例中我們要傳入相關的模型(例如,在我們例子中是傳入 User 模型)以及要檢查的字段七嫌。當驗證器被調用的時候少办,如果定義模型的任何實例匹配表單中提交的值,它將會拋出一個 ValidationError诵原。我們也可以添加一個具有通用默認值的消息英妓,它將會被包含在 ValidationError 中挽放。

現在我們可以修改 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 文檔中有 一些例子腿倚。

渲染表單

WTForms 也能幫助我們?yōu)楸韱武秩境?HTML 表示纯出。WTForms 實現的 Field 字段能夠渲染成該字段的 HTML 表示,所以為了渲染它們敷燎,我們只必須在我們模板中調用表單的字段暂筝。這就像渲染 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>

我們可以自定義如何渲染字段,通過傳入字段的屬性作為參數到調用中饭豹。

<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>
  • 如果我們想要傳入 “class” HTML 屬性鸵赖,我們必須使用 class_='' 因為 “class” 是 Python 中的保留關鍵字。
  • WTForms 文檔中有一個 可用字段屬性列表拄衰。
  • 你可能注意到我們沒有必須要使用 Jinja 的 |safe 過濾器它褪。這是因為 WTForms 渲染 HTML 安全字符串。
  • 更多的內容請參閱 官方文檔翘悉。

摘要

  • 表單從安全性的角度來看是很可怕的茫打。
  • WTForms(以及 Flask-WTF)使得容易地定義,保護以及渲染你的表單镐确。
  • 使用 Flask-WTF 提供的 CSRF 保護可以確保你的表單的安全包吝。
  • 你也可以使用 Flask-WTF 來保護你的 AJAX 調用以防止 CSRF 攻擊。
  • 定義自定義的表單驗證器可以讓驗證邏輯遠離視圖源葫。
  • 使用 WTForms 字段渲染來渲染你的表單的 HTML诗越,在你對你的表單定義做出一些改變的時候,你不必每次都更新它息堂。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末嚷狞,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子荣堰,更是在濱河造成了極大的恐慌床未,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件振坚,死亡現場離奇詭異薇搁,居然都是意外死亡,警方通過查閱死者的電腦和手機渡八,發(fā)現死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門啃洋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來传货,“玉大人,你說我怎么就攤上這事宏娄∥试#” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵孵坚,是天一觀的道長粮宛。 經常有香客問我,道長卖宠,這世上最難降的妖魔是什么巍杈? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮逗堵,結果婚禮上秉氧,老公的妹妹穿的比我還像新娘。我一直安慰自己蜒秤,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布亚斋。 她就那樣靜靜地躺著作媚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪帅刊。 梳的紋絲不亂的頭發(fā)上纸泡,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音赖瞒,去河邊找鬼女揭。 笑死,一個胖子當著我的面吹牛栏饮,可吹牛的內容都是我干的吧兔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼袍嬉,長吁一口氣:“原來是場噩夢啊……” “哼境蔼!你這毒婦竟也來了?” 一聲冷哼從身側響起伺通,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤箍土,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后罐监,有當地人在樹林里發(fā)現了一具尸體吴藻,經...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年弓柱,在試婚紗的時候發(fā)現自己被綠了沟堡。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片侧但。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖弦叶,靈堂內的尸體忽然破棺而出俊犯,到底是詐尸還是另有隱情,我是刑警寧澤伤哺,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布燕侠,位于F島的核電站,受9級特大地震影響立莉,放射性物質發(fā)生泄漏绢彤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一蜓耻、第九天 我趴在偏房一處隱蔽的房頂上張望茫舶。 院中可真熱鬧,春花似錦刹淌、人聲如沸饶氏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽疹启。三九已至,卻和暖如春蔼卡,著一層夾襖步出監(jiān)牢的瞬間喊崖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工雇逞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留荤懂,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓塘砸,卻偏偏與公主長得像节仿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子谣蠢,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容