Python個(gè)人博客程序開(kāi)發(fā)實(shí)例用戶(hù)驗(yàn)證功能
在Python個(gè)人博客程序開(kāi)發(fā)實(shí)例框架設(shè)計(jì)中,我們已經(jīng)完成了 數(shù)據(jù)庫(kù)設(shè)計(jì)、數(shù)據(jù)準(zhǔn)備、模板架構(gòu)、表單設(shè)計(jì)、視圖函數(shù)設(shè)計(jì)、電子郵件支持 等總體設(shè)計(jì)的內(nèi)容。
在Python個(gè)人博客程序開(kāi)發(fā)實(shí)例信息顯示中,我們一起實(shí)現(xiàn)了 顯示文章列表、博客信息、文章內(nèi)容和評(píng)論 等功能。
那么,本篇文章將會(huì)介紹如何 初始化博客、利用 Flask-Login 管理用戶(hù)認(rèn)證、使用 CSRFProtect 實(shí)現(xiàn) CSRF 保護(hù)。
1.安全存儲(chǔ)密碼
創(chuàng)建管理員用戶(hù)需要存儲(chǔ)用戶(hù)名和密碼,密碼的存儲(chǔ)需要特別注意。密碼不能直接以明文的形式存儲(chǔ)在數(shù)據(jù)庫(kù)中,因?yàn)橐坏?shù)據(jù)庫(kù)被竊取或是被攻擊者使用暴 力破解或字典法破解,用戶(hù)的賬戶(hù)、密碼將被直接泄露。如果發(fā)生泄漏,常常會(huì)導(dǎo)致用戶(hù)在其他網(wǎng)站上的賬戶(hù)處于危險(xiǎn)狀態(tài),因?yàn)橥ǔS脩?hù)會(huì)在多個(gè)網(wǎng)站使用同一個(gè)密碼。一般的做法是不存儲(chǔ)密碼本身,而是存儲(chǔ)通過(guò)密碼生成的散列值(hash)。每一個(gè)密碼對(duì)應(yīng)著獨(dú)一無(wú)二的散列值,從而避免明文存儲(chǔ)密碼。
如果只是簡(jiǎn)單地計(jì)算散列值,攻擊者可以使用彩虹表的方式逆向破解密碼。這時(shí)我們需要加鹽計(jì)算散列值。加鹽后,散列值的隨機(jī)性會(huì)顯著提高。但僅僅把鹽和散列值連接在一起可能還不夠,我們還需要使用 HMAC(hash-based message authentication code)
來(lái)重復(fù)計(jì)算很多次(比如 5000 次)最終獲得派生密鑰,這會(huì)增大攻擊者暴 力破解密碼的難度,這種方式被稱(chēng)為 密鑰擴(kuò)展(key stretching
)。
在密碼學(xué)中,鹽(salt)是一串隨機(jī)生成的字符,用來(lái)增加散列值計(jì)算的隨機(jī)性。經(jīng)過(guò)這一系列處理后,即使攻擊者獲取到了密碼的散列值,也無(wú)法逆向獲取真實(shí)的密碼值。在生產(chǎn)環(huán)節(jié)中,盡管對(duì)密碼加密存儲(chǔ)安全性很強(qiáng),仍然需要使用安全的 HTTP 以加密傳輸數(shù)據(jù),避免密碼在傳輸過(guò)程中被截獲。
Werkzeug 在 security
模塊中提供了一個(gè) generate_password_hash(password,method=‘pbkdf2:sha256’,salt_length=8)
函數(shù)用于為給定的密碼生成散列值,參數(shù) method
用來(lái)指定計(jì)算散列值的方法,salt_length
參數(shù)用來(lái)指定鹽(salt)的長(zhǎng)度。security
模塊中的 check_password_hash(pwhash,password)
函數(shù)接收散列值(pwhash)和密碼(password)作為參數(shù),用于檢查密碼散列值與密碼是否對(duì)應(yīng)。
>>> from werkzeug.security import generate_password_hash, check_password_hash >>> password_hash = generate_password_hash('cat') >>> password_hash 'pbkdf2:sha256:50000$mIeMzTvb$ba3c0a274c6b53fda2ab39f864254dfb0a929848b7ec99f81e3bf721d8860fdc' >>> check_password_hash(password_hash, 'dog') False >>> check_password_hash(password_hash, 'cat') True >>> password_hash = generate_password_hash('cat') >>> password_hash 'pbkdf2:sha256:150000$AITKk6jv$5c0b732535cae83677fdf2e666153f82b5db30e6f40ec7a625678ad2b5f4ad25'
generate_password_hash()
函數(shù)生成的密碼散列值的格式如下:
method$salt$hash
因?yàn)樵谟?jì)算散列值時(shí)會(huì)加鹽,而鹽是隨機(jī)生成的,所以即使兩個(gè)用戶(hù)的密碼相同,最終獲得的密碼散列值也是不同的。我們沒(méi)法從密碼散列值逆向獲取密碼,但是如果密碼、計(jì)算方法、鹽相同,最終獲得的散列值結(jié)果也會(huì)是相同的,所以 check_password_hash()
函數(shù)會(huì)根據(jù)密碼散列值中的方法、鹽重新對(duì)傳入的密碼進(jìn)行散列值計(jì)算,然后對(duì)比散列值。
from werkzeug.security import generate_password_hash, check_password_hash class User(db.Model): ... password_hash = db.Column(db.String(128)) ... def set_password(self, password): self.password_hash = generate_password_hash(password) def validate_password(self, password): return check_password_hash(self.password_hash, password)
set_password()
方法用來(lái)設(shè)置密碼,它接收密碼的原始值作為參數(shù),將密碼的散列值設(shè)為 password_hash
的值。validate_password()
方法用于驗(yàn)證密碼是否和對(duì)應(yīng)的散列值相符,返回布爾值。
2.使用Flask-Login管理用戶(hù)認(rèn)證
博客程序需要根據(jù)用戶(hù)的身份開(kāi)放不同的功能,對(duì)于程序使用者——管理員來(lái)說(shuō),他可以撰寫(xiě)文章、管理博客;而普通的用戶(hù)(匿名用戶(hù))則只能閱讀文章、發(fā)表評(píng)論。為了讓程序識(shí)別出用戶(hù)的身份,我們需要添加用戶(hù)認(rèn)證功能。具體來(lái)說(shuō),使用用戶(hù)名和密碼登入博客程序的用戶(hù)被視為管理員,而未登錄的用戶(hù)則被視為匿名用戶(hù)。
擴(kuò)展 Flask-Login 為 Flask 提供了用戶(hù)會(huì)話(huà)管理功能,使用它可以輕松的處理用戶(hù)登錄、登出等操作。
在 extensions.py
腳本中實(shí)例化擴(kuò)展提供的 LoginManager
類(lèi),創(chuàng)建一個(gè) login_manager
或 login
對(duì)象。
from flask_login import LoginManager ... login_manager = LoginManager(app)
然后在程序包的工廠函數(shù)中對(duì) login
對(duì)象調(diào)用 init_app()
方法進(jìn)行初始化擴(kuò)展
login_manager.init_app(app)
Flask-Login 要求表示用戶(hù)的類(lèi)必須實(shí)現(xiàn)下表中所示的這幾個(gè)屬性和方法,以便用來(lái)判斷用戶(hù)的認(rèn)證狀態(tài)。
通過(guò)對(duì)用戶(hù)對(duì)象調(diào)用各種方法和屬性即可判斷用戶(hù)的狀態(tài),比如是否登錄等。方便的做法是讓用戶(hù)類(lèi)繼承 Flask-Login 提供的 UserMixin
類(lèi),它包含了這些方法和屬性的默認(rèn)實(shí)現(xiàn)。
from flask_login import UserMixin class Admin(db.Model, UserMixin): ...
UserMinxin
表示通過(guò)認(rèn)證的用戶(hù),所以 is_authenticated
和 is_active
屬性會(huì)返回 True
,而 is_anonymous
則返回 False
。get_id()
默認(rèn)會(huì)查找用戶(hù)對(duì)象的 id
屬性值作為 id
,而這正是我們的 Admin
類(lèi)中的主鍵字段。
使用 Flask-Login 登入/登出某個(gè)用戶(hù)非常簡(jiǎn)單,只需要在視圖函數(shù)中調(diào)用 Flask-Login 提供的 login_user()
或 logout_user()
函數(shù),并傳入要登入/登出的用戶(hù)類(lèi)對(duì)象。在這兩個(gè)函數(shù)背后,F(xiàn)lask-Login 使用 Flask 的 session
對(duì)象將用戶(hù)的 id
值存儲(chǔ)到用戶(hù)瀏覽器的 cookie
中(名為 user_id
),這時(shí)表示用戶(hù)被登入。相對(duì)來(lái)說(shuō),登出則意味著在用戶(hù)瀏覽器的 cookie
中刪除這個(gè)值。默認(rèn)情況下,關(guān)閉瀏覽器時(shí),通過(guò) Flask 的 session
對(duì)象存儲(chǔ)在客戶(hù)端的 session cookie
會(huì)被刪除,所以用戶(hù)會(huì)登出。
另外,F(xiàn)lask-Login 還支持記住登錄狀態(tài),通過(guò)在 login_user()
中將 remember
參數(shù)設(shè)為 True
即可實(shí)現(xiàn)。這時(shí) Flask-Login 會(huì)在用戶(hù)瀏覽器中創(chuàng)建一個(gè)名為 remember_token
的 cookie
,當(dāng)通過(guò) session
設(shè)置的 user_id cookie
因?yàn)橛脩?hù)關(guān)閉瀏覽器而失效時(shí),它會(huì)重新恢復(fù) user_id cookie
的值。
為了防止破壞 Flask-Login 提供的認(rèn)證功能,我們?cè)谝晥D函數(shù)中操作 session
時(shí)要避免使用 user_id
和 remember_token
作為鍵。remember_token cookie
的默認(rèn)過(guò)期時(shí)間為 365 天。你可以通過(guò)配置變量 REMEMBER_COOKIE_DURATION
進(jìn)行設(shè)置,設(shè)為 datetime.timedelta
對(duì)象即可。
2.1 獲取當(dāng)前用戶(hù)
那么我們?nèi)绾闻袛嘤脩?hù)的認(rèn)證狀態(tài)呢?答案是使用 Flask-Login 提供的 current_user
對(duì)象。它是一個(gè)和 current_app
類(lèi)似的代理對(duì)象(Proxy),表示當(dāng)前用戶(hù)。調(diào)用時(shí)會(huì)返回與當(dāng)前用戶(hù)對(duì)應(yīng)的用戶(hù)模型類(lèi)對(duì)象。因?yàn)?session
中只會(huì)存儲(chǔ)登錄用戶(hù)的 id
,所以為了讓它返回對(duì)應(yīng)的用戶(hù)對(duì)象,我們還需要設(shè)置一個(gè)用戶(hù)加載函數(shù)。這個(gè)函數(shù)需要使用 login_manager.user_loader
裝飾器,它接收用戶(hù) id
作為參數(shù),返回對(duì)應(yīng)的用戶(hù)對(duì)象。
@login_manager.user_loader def load_user(user_id): from bluelog.models import Admin user = Admin.query.get(int(user_id)) return user
現(xiàn)在,當(dāng)我們調(diào)用 current_user
時(shí),F(xiàn)lask-Login 會(huì)調(diào)用用戶(hù)加載函數(shù)并返回對(duì)應(yīng)的用戶(hù)對(duì)象。如果當(dāng)前用戶(hù)已經(jīng)登錄,會(huì)返回 Admin
類(lèi)實(shí)例;如果用戶(hù)未登錄,current_user
默認(rèn)會(huì)返回 Flask-Login 內(nèi)置的 AnonymousUserMixin
類(lèi)對(duì)象,它的 is_authenticated
和 is_active
屬性會(huì)返回 False
,而 is_anonymous
屬性則返回 True
。
current_user
存儲(chǔ)在請(qǐng)求上下文堆棧上,所以只有激活請(qǐng)求上下文程序的情況下才可以使用,比如在視圖函數(shù)中或是模板中調(diào)用。
最終,我們可以通過(guò)對(duì) current_user
對(duì)象調(diào)用 is_authenticated
等屬性來(lái)判斷當(dāng)前用戶(hù)的認(rèn)證狀態(tài)。它也和我們自定義的模板全局變量一樣注入到了模板上下文中,可以在所有模板中使用,所以我們可以在模板中根據(jù)用戶(hù)狀態(tài)渲染不同的內(nèi)容
2.2 登入用戶(hù)
個(gè)人博客的登錄鏈接可以放在次要的位置,因?yàn)橹挥胁┛妥髡卟艜?huì)真正用到它。我們把它放到頁(yè)腳,并根據(jù)用戶(hù)的狀態(tài)來(lái)選擇渲染出不同的鏈接。
<small> {% if current_user.is_authenticated %} <!-- 如果用戶(hù)已經(jīng)登錄,顯示下面的“登出”鏈接--> <a href="{{ url_for('auth.logout', next=request.full_path) }}" rel="external nofollow" >Logout</a> {% else %} <!-- 如果沒(méi)有登錄,則顯示下面的“登錄”按鈕 --> <a href="{{ url_for('auth.login', next=request.full_path) }}" rel="external nofollow" >Login</a> {% endif %} </small>
通過(guò) current_user
的 is_authenticated
值判斷用戶(hù)是否登錄,如果用戶(hù)已登錄(is_authenticated
為 True
)就渲染注銷(xiāo)按鈕,否則就渲染登錄按鈕。按鈕中的 URL 分別指向用于登錄和登出的 login
和 logout
視圖,url_for()
函數(shù)中加入的 next
參數(shù)用來(lái)存儲(chǔ)當(dāng)前頁(yè)面的路徑,以便在執(zhí)行登錄或登出操作后將用戶(hù)重定向回上一個(gè)頁(yè)面。
from flask_login import login_user from bluelog.forms import LoginForm from bluelog.models import Admin from bluelog.utils import redirect_back ... @auth_bp.route('/login', methods=['GET', 'POST']) def login(): if current_user.is_authenticated: return redirect(url_for('blog.index')) form = LoginForm() if form.validate_on_submit(): username = form.username.data password = form.password.data remember = form.remember.data admin = Admin.query.first() if admin: # 驗(yàn)證用戶(hù)名和密碼 if username == admin.username and admin.validate_password(password): login_user(admin, remember) # 登入用戶(hù) flash('Welcome back.', 'info') return redirect_back() # 返回上一個(gè)頁(yè)面 flash('Invalid username or password.', 'warning') else: flash('No account.', 'warning') return render_template('auth/login.html', form=form)
登錄視圖負(fù)責(zé)渲染 login.html
模板和驗(yàn)證登錄表單。在函數(shù)一開(kāi)始,為了避免已經(jīng)登錄的用戶(hù)不小心訪(fǎng)問(wèn)這個(gè)視圖,我們添加一個(gè)if判斷將已經(jīng)登錄的用戶(hù)重定向到首頁(yè)。
與其他表單處理流程相同,當(dāng)用戶(hù)提交表單且數(shù)據(jù)通過(guò)驗(yàn)證后,我們分別從表單中獲取用戶(hù)名(username)、密碼(password)和 “記住我”(remember)字段的數(shù)據(jù)。接著,從數(shù)據(jù)庫(kù)中查詢(xún)出 Admin
對(duì)象,判斷 username
的值,并使用 Admin
類(lèi)中的 validate_password()
方法驗(yàn)證密碼。如果通過(guò)驗(yàn)證就調(diào)用 login_user()
方法登錄用戶(hù),傳入用戶(hù)對(duì)象和 remember
字段的值作為參數(shù),最后使用 redirect_back()
函數(shù)重定向回上一個(gè)頁(yè)面;如果用戶(hù)名和密碼驗(yàn)證出錯(cuò)就發(fā)送錯(cuò)誤提示,并渲染模板。另外,如果 Admin
對(duì)象不存在,就發(fā)送一個(gè)提示消息,然后重新渲染表單。
登錄表單 LoginForm
在新創(chuàng)建的 login.html
模板中使用 Bootstrap-Flask 提供的 render_form()
宏渲染。為了編寫(xiě)一個(gè)更簡(jiǎn)單的登錄頁(yè)面,我們打算不在登錄頁(yè)面顯示頁(yè)腳,因?yàn)槲覀冊(cè)诨0逯袨轫?yè)腳的代碼定義了 footer
塊,所以在登錄頁(yè)面模板只需要定義這個(gè)塊并留空就可以覆蓋基模板中的對(duì)應(yīng)內(nèi)容。
{% extends 'base.html' %} {% from 'bootstrap/form.html' import render_form %} {% block title %}Login{% endblock %} {% block content %} <div class="container h-100"> <div class="row h-100 page-header justify-content-center align-items-center"> <h1>Log in</h1> </div> <div class="row h-100 justify-content-center align-items-center"> {{ render_form(form, extra_classes='col-6') }} </div> </div> {% endblock %} {% block footer %}{% endblock %}
2.3 登出用戶(hù)
注銷(xiāo)登錄比登錄還要簡(jiǎn)單,只需要調(diào)用 Flask-Login 提供的 logout_user()
函數(shù)即可。這會(huì)登出用戶(hù)并清除 session
中存儲(chǔ)的用戶(hù) id
和 “記住我” 的值。
from flask_login import logout_user ... @auth_bp.route('/logout') @login_required def logout(): logout_user() flash('Logout success.', 'info') return redirect_back()
2.4 視圖保護(hù)
程序中的許多操作要求用戶(hù)登錄后才能進(jìn)行,因此我們要把這些需要登錄才能訪(fǎng)問(wèn)的視圖 “保護(hù)” 起來(lái)。如果用戶(hù)訪(fǎng)問(wèn)了某個(gè)需要認(rèn)證才能訪(fǎng)問(wèn)的資源,我們不會(huì)返回對(duì)應(yīng)的響應(yīng),而是把程序重定向到登錄頁(yè)面。
視圖保護(hù)可以使用 Flask-Login 提供的 login_required
裝飾器實(shí)現(xiàn)。在需要登錄才能訪(fǎng)問(wèn)的視圖前附加這個(gè)裝飾器,比如博客設(shè)置頁(yè)面。
當(dāng)為視圖函數(shù)附加多個(gè)裝飾器時(shí),route()
裝飾器應(yīng)該置于最外層。
from flask_login import login_required @admin_bp.route('/settings') @login_required def settings(): ... return render_template('admin/settings.html')
當(dāng)未登錄的用戶(hù)訪(fǎng)問(wèn)使用了 login_required
裝飾器的視圖時(shí),程序會(huì)自動(dòng)重定向到登錄視圖,并閃現(xiàn)一個(gè)消息提示。在此之前,我們還需要在 extension.py
腳本中使用 login_manager
對(duì)象的 login_view
屬性設(shè)置登錄視圖的端點(diǎn)值(包含藍(lán)本名的完整形式)。
login_manager = LoginManager(app) ... login_manager.login_view = 'auth.login' login_manager.login_message_category = 'warning'
使用可選的 login_message_category
屬性可以設(shè)置消息的類(lèi)別,默認(rèn)類(lèi)別為 “message”。另外,使用可選的 login_message
屬性設(shè)置提示消息的內(nèi)容,默認(rèn)消息內(nèi)容為“Please log in to access this page.”
當(dāng)用戶(hù)訪(fǎng)問(wèn)某個(gè)被保護(hù)的 URL 時(shí),在重定向后的登錄 URL 中,F(xiàn)lask-Login 會(huì)自動(dòng)附加一個(gè)包含上一個(gè)頁(yè)面 URL 的 next
參數(shù),所以我們只需要使用 redirect_back()
函數(shù)就可以將登錄成功后的用戶(hù)重定向回上一個(gè)頁(yè)面。
當(dāng)在未登錄狀態(tài)下訪(fǎng)問(wèn)設(shè)置頁(yè)面 http://localhost:5000/admin/settings 時(shí),程序會(huì)重定向到登錄頁(yè)面,并顯示提示消息,URL 中包含上一個(gè)頁(yè)面的 next
參數(shù)。
仔細(xì)觀察地址欄,你會(huì)看到附加的 next
參數(shù)包含上一個(gè)頁(yè)面的地址,我們經(jīng)常在上網(wǎng)時(shí)在地址欄發(fā)現(xiàn)類(lèi)似的參數(shù),比如 ReturnUrl
、RedirectUrl
等。當(dāng)我們登錄后,程序會(huì)重定向回我們想要訪(fǎng)問(wèn)的設(shè)置頁(yè)面。
有些時(shí)候,你會(huì)希望為整個(gè)藍(lán)本添加登錄保護(hù)。比如,管理后臺(tái)的所有頁(yè)面都需要登錄后才能訪(fǎng)問(wèn),也就是說(shuō),我們需要為所有 admin
藍(lán)本中的視圖函數(shù)附加 login_required
裝飾器。有一個(gè)小技巧可以避免這些重復(fù):為 admin
藍(lán)本注冊(cè)一個(gè) before_request
處理函數(shù),然后為這個(gè)函數(shù)附加 login_required
裝飾器。因?yàn)槭褂?before_request
鉤子注冊(cè)的函數(shù)會(huì)在每一個(gè)請(qǐng)求前運(yùn)行,所以這樣就可以為該藍(lán)本下所有的視圖函數(shù)添加保護(hù),函數(shù)內(nèi)容可以為空。
@admin_bp.before_request @login_required def login_protect(): pass
- 雖然這個(gè)技巧很方便,但是為了避免在書(shū)中單獨(dú)給出視圖函數(shù)代碼時(shí)造成誤解,Bluelog程序中并沒(méi)有使用這個(gè)技巧。
- 如果沒(méi)有使用這個(gè)技巧,那么
admin
藍(lán)本下的所有視圖都需要添加login_required
裝飾器,否則會(huì)導(dǎo)致博客資源被匿名用戶(hù)修改。
3.使用CSRFProtect實(shí)現(xiàn)CSRF保護(hù)
CSRF攻擊,全稱(chēng)為 Cross-site request forgery
,中文名為 跨站請(qǐng)求偽造,也被稱(chēng)為 One Click Attack
或者 Session Riding
,通??s寫(xiě)為 CSRF
或者 XSRF
,是一種對(duì)網(wǎng)站的惡意利用。XSS 主要是利用站點(diǎn)內(nèi)的信任用戶(hù),而 CSRF 則通過(guò)偽裝來(lái)自受信任用戶(hù)的請(qǐng)求,來(lái)利用受信任的網(wǎng)站。與 XSS 相比,CSRF 更具危險(xiǎn)性。攻擊者盜用用戶(hù)身份,發(fā)送惡意請(qǐng)求。比如:模擬用戶(hù)發(fā)送郵件,發(fā)消息,以及支付、轉(zhuǎn)賬等。
博客管理后臺(tái)會(huì)涉及對(duì)資源的局部更新和刪除操作,這時(shí)我們就要考慮到 CSRF 保護(hù)問(wèn)題。為了應(yīng)對(duì) CSRF 攻擊,當(dāng)需要?jiǎng)?chuàng)建、修改和刪除數(shù)據(jù)時(shí),我們需要將這類(lèi)請(qǐng)求通過(guò) POST 方法提交,同時(shí)在提交請(qǐng)求的表單中添加 CSRF 令牌。對(duì)于刪除和某些修改操作來(lái)說(shuō),單獨(dú)創(chuàng)建表單類(lèi)的流程太過(guò)煩瑣,我們可以使用 Flask-WTF 內(nèi)置的 CSRFProtect
擴(kuò)展為這類(lèi)操作實(shí)現(xiàn)更簡(jiǎn)單和完善的 CSRF 保護(hù)。
CSRFProtect
是 Flask-WTF 內(nèi)置的擴(kuò)展,也是 Flask-WTF 內(nèi)部使用的 CSRF 組件,單獨(dú)使用可以實(shí)現(xiàn)對(duì)程序的全局 CSRF 保護(hù)。它主要提供了生成和驗(yàn)證 CSRF 令牌的函數(shù),方便在不使用 WTForms 表單類(lèi)的情況下實(shí)現(xiàn) CSRF 保護(hù)。因?yàn)槲覀円呀?jīng)安裝了 Flask-WTF,所以可以直接使用它。首先在 extensions.py
腳本中實(shí)例化 Flask-WTF
提供的 CSRFProtect
類(lèi)。
from flask_wtf.csrf import CSRFProtect ... csrf = CSRFProtect() ...
在程序包的構(gòu)造文件中初始化擴(kuò)展 CSRFProtect
:
from bluelog.extensions import csrf def create_app(config_name=None): ... register_extensions(app) return app def register_extensions(app): ... csrf.init_app(app)
CSRFProtect
在模板中提供了一個(gè) csrf_token()
函數(shù),用來(lái)生成 CSRF 令牌值,我們直接在表單中創(chuàng)建這個(gè)隱藏字段,將這個(gè)字段的 name
值設(shè)為 csrf_token
。下面是用來(lái)刪除文章的表單示例:
<form method="post" action="{{ url_for('.delete_post', post_id=post.id) }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input type="submit" value="Delete Post"/> </form>
在對(duì)應(yīng)的 delete_post
視圖中,我們直接執(zhí)行相關(guān)刪除操作,CSRFProtect
會(huì)自動(dòng)獲取并驗(yàn)證 CSRF 令牌。注意,在 app.route()
裝飾器中使用 methods 參數(shù)限制僅監(jiān)聽(tīng) POST 請(qǐng)求。
@app.route('/post/delete/<id>', methods=['POST']) def delete_post(id): post = Post.query.get(id) post.delete() return redirect(url_for('index'))
默認(rèn)情況下,當(dāng)令牌驗(yàn)證出錯(cuò)或過(guò)期時(shí),程序會(huì)返回 400 錯(cuò)誤,和 Werkzeug 內(nèi)置的其他 HTTP 異常類(lèi)一樣,CSRFError
將錯(cuò)誤描述保存在異常對(duì)象的 description
屬性中。
如果你想將與 CSRF 相關(guān)的錯(cuò)誤描述顯示在模板中,那么你可以在 400 錯(cuò)誤處理函數(shù)中將異常對(duì)象的 description
屬性傳入模板,也可以單獨(dú)創(chuàng)建一個(gè)錯(cuò)誤處理函數(shù)捕捉令牌出錯(cuò)時(shí)拋出的 CSRFError
異常。
from flask_wtf.csrf import CSRFError def register_errors(app): ... @app.errorhandler(CSRFError) def handle_csrf_error(e): return render_template('400.html', description=e.description), 400
這個(gè)錯(cuò)誤處理函數(shù)仍然使用 app.errorhandler
裝飾器注冊(cè),傳入 flask_wtf.csrf
模塊中的 CSRFError
類(lèi)。這個(gè)錯(cuò)誤處理函數(shù)返回 400 錯(cuò)誤響應(yīng),通過(guò)異常對(duì)象的 description
屬性獲取內(nèi)置的錯(cuò)誤消息(英文),傳入模板 400.html
中。在模板中,我們渲染這個(gè)錯(cuò)誤消息,并為常規(guī) 400 錯(cuò)誤設(shè)置一個(gè)默認(rèn)值。
<p>{<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->{ description|default('Bad Request') }}</p>
在實(shí)際應(yīng)用中,除了使用內(nèi)置的錯(cuò)誤描述,更合適的方法是自己編寫(xiě)錯(cuò)誤描述信息。默認(rèn)的錯(cuò)誤描述為 “Invalid CSRF token.” 和 “The CSRF token is missing.” 因?yàn)榘嘈g(shù)語(yǔ),不容易理解,所以在實(shí)際的程序中,我們應(yīng)該使用更簡(jiǎn)單的錯(cuò)誤提示,比如 “會(huì)話(huà)過(guò)期或失效,請(qǐng)返回上一頁(yè)面重試”。
到此這篇關(guān)于Python個(gè)人博客程序開(kāi)發(fā)實(shí)例用戶(hù)驗(yàn)證功能的文章就介紹到這了,更多相關(guān)Python個(gè)人博客內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python數(shù)據(jù)類(lèi)型詳解(二)列表
本文給大家詳細(xì)介紹的是Python數(shù)據(jù)類(lèi)型中的列表(list),非常的簡(jiǎn)單實(shí)用,有需要的小伙伴可以參考下2016-05-05Python學(xué)習(xí)之基礎(chǔ)語(yǔ)法介紹
大家好,本篇文章主要講的是Python學(xué)習(xí)之基礎(chǔ)語(yǔ)法介紹,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話(huà)記得收藏一下,方便下次瀏覽2021-12-12一文詳細(xì)介紹PyQt5 QPushButton() 的作用
通過(guò)本文的介紹,相信你已經(jīng)對(duì)PyQt5中的QPushButton控件有了深入的了解,從基礎(chǔ)介紹到常用屬性和方法,再到應(yīng)用場(chǎng)景和樣式定制,本文為你提供了全面的指南,感興趣的朋友跟隨小編一起看看吧2024-08-08