Flask扩展系列(八)–用户会话管理
安裝和啟用
遵循標(biāo)準(zhǔn)的Flask擴(kuò)展安裝和啟用方式,先通過pip來安裝擴(kuò)展:
$ pip install Flask-Login接下來創(chuàng)建擴(kuò)展對(duì)象實(shí)例:
| 1 2 3 4 5 | from flask import Flask from flask.ext.login import LoginManager ? app = Flask(__name__) login_manager = LoginManager(app) |
?
同時(shí),你可以對(duì)LoginManager對(duì)象賦上配置參數(shù):
| 1 2 3 4 5 6 7 8 9 | # 設(shè)置登錄視圖的名稱,如果一個(gè)未登錄用戶請(qǐng)求一個(gè)只有登錄用戶才能訪問的視圖, # 則閃現(xiàn)一條錯(cuò)誤消息,并重定向到這里設(shè)置的登錄視圖。 # 如果未設(shè)置登錄視圖,則直接返回401錯(cuò)誤。 login_manager.login_view = 'login' # 設(shè)置當(dāng)未登錄用戶請(qǐng)求一個(gè)只有登錄用戶才能訪問的視圖時(shí),閃現(xiàn)的錯(cuò)誤消息的內(nèi)容, # 默認(rèn)的錯(cuò)誤消息是:Please log in to access this page.。 login_manager.login_message = 'Unauthorized User' # 設(shè)置閃現(xiàn)的錯(cuò)誤消息的類別 login_manager.login_message_category = "info" |
?
編寫用戶類
使用Flask-Login之前,你需要先定義用戶類,該類必須實(shí)現(xiàn)以下三個(gè)屬性和一個(gè)方法:
當(dāng)用戶登錄成功后,該屬性為True。
如果該用戶賬號(hào)已被激活,且該用戶已登錄成功,則此屬性為True。
是否為匿名用戶(未登錄用戶)。
每個(gè)用戶都必須有一個(gè)唯一的標(biāo)識(shí)符作為ID,該方法可以返回當(dāng)前用戶的ID,這里ID必須是Unicode。
因?yàn)槊看螌憘€(gè)用戶類很麻煩,Flask-Login提供了”UserMixin”類,你可以直接繼承它即可:
| 1 2 3 4 | from flask.ext.login import UserMixin ? class User(UserMixin): ????pass |
?
從會(huì)話或請(qǐng)求中加載用戶
在編寫登錄登出視圖前,我們要先寫一個(gè)加載用戶對(duì)象的方法。它的功能是根據(jù)傳入的用戶ID,構(gòu)造一個(gè)新的用戶類的對(duì)象。為了簡(jiǎn)化范例,我們不引入數(shù)據(jù)庫(kù),而是在列表里定義用戶記錄。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # 用戶記錄表 users = [ ????{'username': 'Tom', 'password': '111111'}, ????{'username': 'Michael', 'password': '123456'} ] ? # 通過用戶名,獲取用戶記錄,如果不存在,則返回None def query_user(username): ????for user in users: ????????if user['username'] == username: ????????????return user ? # 如果用戶名存在則構(gòu)建一個(gè)新的用戶類對(duì)象,并使用用戶名作為ID # 如果不存在,必須返回None @login_manager.user_loader def load_user(username): ????if query_user(username) is not None: ????????curr_user = User() ????????curr_user.id = username ????????return curr_user |
上述代碼中,通過”@login_manager.user_loader”裝飾器修飾的方法,既是我們要實(shí)現(xiàn)的加載用戶對(duì)象方法。它是一個(gè)回調(diào)函數(shù),在每次請(qǐng)求過來后,Flask-Login都會(huì)從Session中尋找”user_id”的值,如果找到的話,就會(huì)用這個(gè)”user_id”值來調(diào)用此回調(diào)函數(shù),并構(gòu)建一個(gè)用戶類對(duì)象。因此,沒有這個(gè)回調(diào)的話,Flask-Login將無法工作。
有一個(gè)問題,啟用Session的話一定需要客戶端允許Cookie,因?yàn)镾ession ID是保存在Cookie中的,如果Cookie被禁用了怎么辦?那我們的應(yīng)用只好通過請(qǐng)求參數(shù)將用戶信息帶過來,一般情況下會(huì)使用一個(gè)動(dòng)態(tài)的Token來表示登錄用戶的信息。此時(shí),我們就不能依靠”@login_manager.user_loader”回調(diào),而是使用”@login_manager.request_loader”回調(diào)。
| 1 2 3 4 5 6 7 8 9 10 11 | from flask import request ? # 從請(qǐng)求參數(shù)中獲取Token,如果Token所對(duì)應(yīng)的用戶存在則構(gòu)建一個(gè)新的用戶類對(duì)象 # 并使用用戶名作為ID,如果不存在,必須返回None @login_manager.request_loader def load_user_from_request(request): ????username = request.args.get('token') ????if query_user(username) is not None: ????????curr_user = User() ????????curr_user.id = username ????????return curr_user |
為了簡(jiǎn)化代碼,上面的例子就直接使用用戶名作為Token了,實(shí)際項(xiàng)目中,大家還是要用一個(gè)復(fù)雜的算法來驗(yàn)證Token。
登錄及登出
一切準(zhǔn)備就緒,我們開始實(shí)現(xiàn)登錄視圖:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | from flask import render_template, redirect, url_for, flash from flask.ext.login import login_user ? @app.route('/login', methods=['GET', 'POST']) def login(): ????if request.method == 'POST': ????????username = request.form.get('username') ????????user = query_user(username) ????????# 驗(yàn)證表單中提交的用戶名和密碼 ????????if user is not None and request.form['password'] == user['password']: ????????????curr_user = User() ????????????curr_user.id = username ? ????????????# 通過Flask-Login的login_user方法登錄用戶 ????????????login_user(curr_user) ? ????????????# 如果請(qǐng)求中有next參數(shù),則重定向到其指定的地址, ????????????# 沒有next參數(shù),則重定向到"index"視圖 ????????????next = request.args.get('next') ????????????return redirect(next or url_for('index')) ? ????????flash('Wrong username or password!') ????# GET 請(qǐng)求 ????return render_template('login.html') |
上述代碼同之前Login視圖最大的不同就是你在用戶驗(yàn)證通過后,需要調(diào)用Flask-Login擴(kuò)展提供的”login_user()”方法來讓用戶登錄,該方法需傳入用戶類對(duì)象。這個(gè)”login_user()”方法會(huì)幫助你操作用戶Session,并且會(huì)在請(qǐng)求上下文中記錄用戶信息。另外,在具體實(shí)現(xiàn)時(shí),建議大家對(duì)”next”參數(shù)值作驗(yàn)證,避免被URL注入攻擊。
“l(fā)ogin.html”模板很簡(jiǎn)單,就是顯示一個(gè)用戶名密碼的表單:
| 1 2 3 4 5 6 7 8 9 10 11 | <!doctype html> <title>Login Sample</title> <h1>Login</h1> {% with messages = get_flashed_messages() %} ????<div>{{ messages[0] }}</div> {% endwith %} <form action="{{ url_for('login') }}" method="POST"> ????<input type="text" name="username" id="username" placeholder="Username"></input> ????<input type="password" name="password" id="password" placeholder="Password"></input> ????<input type="submit" name="submit"></input> </form> |
?
接下來,讓我們寫個(gè)index視圖:
| 1 2 3 4 5 6 | from flask.ext.login import current_user, login_required ? @app.route('/') @login_required def index(): ????return 'Logged in as: %s' % current_user.get_id() |
裝飾器”@login_required”就如同我們?cè)谶M(jìn)階系列第四篇中寫的一樣,確保只有登錄用戶才能訪問這個(gè)index視圖,Flask-Login幫我們實(shí)現(xiàn)了這個(gè)裝飾器。如果用戶未登錄,它就會(huì)將頁(yè)面重定向到登錄視圖,也就是我們?cè)诘谝还?jié)中配置的”login_manager.login_view”的視圖。
同時(shí),重定向的地址會(huì)自動(dòng)加上”next”參數(shù),參數(shù)的值是當(dāng)前用戶請(qǐng)求的地址,這樣,登錄成功后就會(huì)跳轉(zhuǎn)回當(dāng)前視圖。可以看到我們對(duì)于用戶登錄所需要的操作,這個(gè)裝飾器基本都實(shí)現(xiàn)了,很方便吧!
Flask-Login還提供了”current_user”代理,可以訪問到登錄用戶的用戶類對(duì)象。我們?cè)谀0逯幸部梢允褂眠@個(gè)代理。讓我們?cè)賹懸粋€(gè)home視圖:
| 1 2 3 4 | @app.route('/home') @login_required def home(): ????return render_template('hello.html') |
模板代碼如下:
| 1 2 3 4 5 | <!doctype html> <title>Login Sample</title> {% if current_user.is_authenticated %} ??<h1>Hello {{ current_user.get_id() }}!</h1> {% endif %} |
在上面的模板代碼中,我們直接訪問了”current_user”對(duì)象的屬性和方法。
登出視圖也很簡(jiǎn)單,Flask-Login提供了”logout_user()”方法來幫助你清理用戶Session。
| 1 2 3 4 5 6 7 | from flask.ext.login import logout_user ? @app.route('/logout') @login_required def logout(): ????logout_user() ????return 'Logged out successfully!' |
?
自定義未授權(quán)訪問的處理方法
“@login_required”裝飾器對(duì)于未登錄用戶訪問的默認(rèn)處理是重定向到登錄視圖,如果我們不想它這么做的話,可以自定義處理方法:
| 1 2 3 | @login_manager.unauthorized_handler def unauthorized_handler(): ????return 'Unauthorized' |
這個(gè)”@login_manager.unauthorized_handler”裝飾器所修飾的方法就會(huì)代替”@login_required”裝飾器的默認(rèn)處理方法。有了上面的代碼,當(dāng)未登錄用戶訪問index視圖時(shí),頁(yè)面就會(huì)直接返回”Unauthorized”信息。
Remember Me
在登錄視圖中,調(diào)用”login_user()”方法時(shí),傳入”remember=True”參數(shù),即可實(shí)現(xiàn)“記住我”功能:
| 1 2 3 | ... ????????????login_user(curr_user, remember=True) ... |
Flask-Login是通過在Cookie實(shí)現(xiàn)的,它會(huì)在Cookie中添加一個(gè)”remember_token”字段來記住之前登錄的用戶信息,所以禁用Cookie的話,該功能將無法工作。
Fresh登錄
當(dāng)用戶通過賬號(hào)和密碼登錄后,Flask-Login會(huì)將其標(biāo)識(shí)為Fresh登錄,即在Session中設(shè)置”_fresh”字段為True。而用戶通過Remember Me自動(dòng)登錄的話,則不標(biāo)識(shí)為Fresh登錄。對(duì)于”@login_required”裝飾器修飾的視圖,是否Fresh登錄都可以訪問,但是有些情況下,我們會(huì)強(qiáng)制要求用戶登錄一次,比如修改登錄密碼,這時(shí)候,我們可以用”@fresh_login_required”裝飾器來修飾該視圖。這樣,通過Remember Me自動(dòng)登錄的用戶,將無法訪問該視圖:
| 1 2 3 4 5 6 | from flask.ext.login import fresh_login_required ? @app.route('/home') @fresh_login_required def home(): ????return 'Logged in as: %s' % current_user.get_id() |
?
會(huì)話保護(hù)
Flask-Login自動(dòng)啟用會(huì)話保護(hù)功能。對(duì)于每個(gè)請(qǐng)求,它會(huì)驗(yàn)證用戶標(biāo)識(shí),這個(gè)標(biāo)識(shí)是由客戶端IP地址和User Agent的值經(jīng)SHA512編碼而來。在用戶登錄成功時(shí),Flask-Login就會(huì)將這個(gè)值保存起來以便后續(xù)檢查。默認(rèn)的會(huì)話保護(hù)模式是”basic”,為了加強(qiáng)安全性,你可以啟用強(qiáng)會(huì)話保護(hù)模式,方法是配置LoginManager實(shí)例對(duì)象中的”session_protection”屬性:
| 1 | login_manager.session_protection = "strong" |
在”strong”模式下,一旦用戶標(biāo)識(shí)檢查失敗,便會(huì)清空所用Session內(nèi)容,并且Remember Me也失效。而”basic”模式下,只是將登錄標(biāo)為非Fresh登錄。你還可以將”login_manager.session_protection”置為None來取消會(huì)話保護(hù)。
更多參考資料
Flask-Login的官方文檔
Flask-Login的源碼
本篇的示例代碼可以在這里下載。
格式日志發(fā)布于2016年4月29日作者Billy.J.Hee分類Python、編程標(biāo)簽Flask、Login、Session
文章導(dǎo)航
上一上篇文章:Flask擴(kuò)展系列(七)–表單
下一下篇文章:Flask擴(kuò)展系列(九)–HTTP認(rèn)證
《Flask擴(kuò)展系列(八)–用戶會(huì)話管理》有3個(gè)想法
張瑩說道:
2017年5月7日 下午9:42
利用Flask-Login和session進(jìn)行用戶登錄管理具體區(qū)別是什么呢?
如何利用session記錄其他需要保留的信息,例如上次訪問頁(yè)面的時(shí)間?
多謝賜教!
總結(jié)
以上是生活随笔為你收集整理的Flask扩展系列(八)–用户会话管理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Proteus仿真单片机:51单片机的仿
- 下一篇: Mysql不同存储引擎的表转换方法