Flask 第三方組件之 login

在了解使用Flask來實現用戶認證之前,我們首先要明白用戶認證的原理。假設現在我們自己去實現用戶認證,需要做哪些事情呢?

  1. 首先,登錄。用戶能夠輸入用戶名和密碼進行登錄,所以需要網頁和表單,實現用戶輸入和提交的過程。
  2. 接著,校驗登錄是否成功。用戶提交了用戶名和密碼,后臺需要比對用戶名密碼是否正確,而要想比對,首先系統中就要有存儲用戶名密碼的地方,大多數后臺系統會通過數據庫來存儲,也可以存儲到文件當中。存儲用戶名密碼需要加密存儲尤其是密碼,如果只是簡單的用明文存儲,很容易被“有心人”盜取,從而造成用戶信息泄露
  3. 登錄之后,我們需要維持用戶登錄狀態,以便用戶在訪問特定網頁的時候來判斷用戶是否已經登錄,以及是否有權限訪問改網頁。這需要維護一個會話來保存用戶的登錄狀態和用戶信息。
  4. 從第三步我們也可以看出,如果我們的網頁需要權限保護,那么當請求到來的時候,我們首先要檢查用戶的信息,比如是否已經登錄,是否有權限等,如果檢查通過,那么在response的時候就會將相應網頁回復給請求的用戶,但是如果檢查不通過,那么就需要返回錯誤信息。
  5. 用戶登出

flask通常是使用Flask-Login模塊來實現上述流程控制。下面介紹使用Flask-Login登錄注銷,以及幫助大家解答一些可能比較常見的問題。

代碼實現

首先,先概述下例子,有三個url,分別是:

/auth/login???? 用于登錄
/auth/logout????用于注銷
/test???????????用于測試,需要登錄才能訪問

安裝必要的庫

pip install Flask==0.10.1
pip install Flask-Login==0.3.2
pip install Flask-WTF==0.12
pip install WTForms==2.1

編寫web框架。在開始登錄之前,我們先把整個 web 的框架搭建出來,也就是,我們要能夠先在不登錄的情況下訪問到上面提到的三個url,我就直接放在一個叫做 app.py 的文件中。

#!/usr/bin/env python
# encoding: utf-8
from flask import Flask, Blueprintapp = Flask(__name__)# url redirect
auth = Blueprint('auth', __name__)@auth.route('/login', methods=['GET', 'POST'])
def login():return "login page"@auth.route('/logout', methods=['GET', 'POST'])
def logout():return "logout page"????# test method
@app.route('/test')
def test():return "yes , you are allowed"app.register_blueprint(auth, url_prefix='/auth')
app.run(debug=True)

現在,我們可以嘗試一下運行一下這個框架,使用 python app.py?運行即可,然后打開瀏覽器,分別訪問一下,看一下是否都正常

http://localhost:5000/test
http://localhost:5000/auth/login
http://localhost:5000/auth/logout

設置登錄才能查看。現在框架已經設置完畢,我們可以將 test 和 auth/logout 這兩個 page 設置成登錄之后才能查看。因為這個功能已經和 login 有關系了,所以這時我們就需要使用到 Flask-Login了。代碼如下

#!/usr/bin/env python
# encoding: utf-8
from flask import Flask, Blueprint
from flask.ext.login import LoginManager, login_requiredapp = Flask(__name__)#################### 以下這段是新增加的 #################### 
app.secret_key = 's3cr3t'
login_manager = LoginManager()# 設置不同的安全等級防止用戶會話遭篡改,屬性可以設為None、basic或strong
# 設為 strong 時,Flask-Login 會記錄客戶端 IP 地址和瀏覽器的用戶代理信息,如果發現異動就登出用戶
login_manager.session_protection = 'strong' # 如果未登錄,返回的頁面
login_manager.login_view = 'auth.login'
login_manager.init_app(app)# Flask-Login 要求程序實現一個回調函數,使用指定的標識符加載用戶。加載用戶的回調函數接收以 Unicode 字符串形式表示的用戶標識符。如果能找到用戶,這個函數必須返回用戶對象;否則應該返回 None,這里因為設置框架所以就默認返回 None。
@login_manager.user_loader
def load_user(user_id):return None
#################### 以上這段是新增加的 #################### auth = Blueprint('auth', __name__)@auth.route('/login', methods=['GET', 'POST'])
def login():return "login page"# 通過Flask-Login提供的login_required裝飾器來增加路由保護,如果未認證用戶訪問這個路由,Flask-Login會將這個請求發往登錄頁面
@auth.route('/logout', methods=['GET', 'POST'])
@login_required
def logout():return "logout page"# test method
@app.route('/test')
@login_required
def test():return "yes , you are allowed"app.register_blueprint(auth, url_prefix='/auth')
app.run(debug=True)

其實我們就增加了兩項代碼,一項是初始化 LoginManager 的, 另外一項就是給 test 和 auth.logout 添加了 login_required 的裝飾器,表示要登錄了才能訪問。注意 login_required 必須放在 auth.route 后面

#################### 部分源碼 ####################
@app.route('/test', methods=['GET', 'POST'])
@csrf.exempt
@login_required
def test():pass
# test= app.route('/test', methods=['GET', 'POST'])(test)
# test= login_required(test)# login_required 源碼
def login_required(func):@wraps(func)def decorated_view(*args, **kwargs):if current_app.login_manager._login_disabled:return func(*args, **kwargs)elif not current_user.is_authenticated:return current_app.login_manager.unauthorized()return func(*args, **kwargs)return decorated_view# app.route 實際最后執行代碼
def app.route() if view_func is not None:old_func = self.view_functions.get(endpoint)if old_func is not None and old_func != view_func:raise AssertionError('View function mapping is overwriting an existing endpoint function: %s' % endpoint)self.view_functions[endpoint] = view_func#################### 分析 ####################
# 原因,正常情況下裝飾器需要將函數地址傳入并返回一個新的函數地址,但是 app.route 創建了一個新的結構并將傳入的函數地址直接保存到結構中,導致其他的裝飾器對這個函數地址修改影響不到 app.route 創建的結構,而在路由分發的時候,直接調用的是結構中保存的地址,所以其他裝飾器不起作用,所以必須將裝飾器放在 app.route 下面#################### 簡化代碼 ####################
def a():return 1def b():return 2c = a
a = b
print(c())

用戶授權。到此,我們發現?test 是不能訪問的,會被重定向到 login 的那個 page。看一下現在的代碼, login_required 有了, 那么就差login了,接下來寫login,看Flask-Login的文檔發現一個叫做login_user的函數,看看它的原型:

flask.ext.login.login_user(user, remember=False, force=False, fresh=True)

這里需要一個user的對象,所以先創建一個Model,其實這個Model還是有一點講究的,最好是繼承自Flask-Login的UserMixin,然后需要實現幾個方法,Model 為:

# user models
class User(UserMixin):def is_authenticated(self):return Truedef is_actice(self):return Truedef is_anonymous(self):return Falsedef get_id(self):return "1"

這里給所有的函數都返回了默認值,默認對應的情況是這個用戶已經登錄,并且是有效的。

然后在 login 的 view 里面 login_user, logout的view里面logout_user,這樣整個登錄過程就連接起來了,最后的代碼是這樣的:

#!/usr/bin/env python
# encoding: utf-8from flask import Flask,Blueprint
from flask.ext.login import LoginManager,login_required,login_user,logout_user,UserMixinapp = Flask(__name__)# user models
class User(UserMixin):def is_authenticated(self):return Truedef is_actice(self):return Truedef is_anonymous(self):return Falsedef get_id(self):return "1"# flask-login
app.secret_key = 's3cr3t'
login_manager = LoginManager()
login_manager.session_protection = 'strong'
login_manager.login_view = 'auth.login'
login_manager.init_app(app)@login_manager.user_loader
def load_user(user_id):user = User()return userauth = Blueprint('auth', __name__)@auth.route('/login', methods=['GET', 'POST'])
def login():user = User()login_user(user)return "login page"@auth.route('/logout', methods=['GET', 'POST'])
@login_required
def logout():logout_user()return "logout page"@app.route('/test')
@login_required
def test():return "yes , you are allowed"app.register_blueprint(auth, url_prefix='/auth')
app.run(debug=True)

總結

到此,這就是一個比較精簡的Flask-Login 教程了,通過這個框架大家可以自行擴展達到更豐富的功能,諸如發送確認郵件,密碼重置,權限分級管理等,這些功能都可以通過flask及其插件來完成,這個大家可以自己探索下。

問題

1、未登錄訪問鑒權頁面如何處理

如果未登錄訪問了一個做了 login_required 限制的 view,那么 flask-login 會默認 flash 一條消息,并且將重定向到 login view, 如果你沒有指定 login view, 那么 flask-login 將會拋出一個401錯誤。指定 login view 只需要直接設置login_manager即可:

login_manager.login_view = "auth.login"

2、自定義flash消息

login_manager.login_message = u"請登錄!"       # 自定義 flash 的消息
login_manager.login_message_category = "info"  #  flash 消息的級別,一般設置成 info 或者 error

?

3、自定義未登錄處理函數

如果你不想使用默認的規則,那么你也可以自定義未登錄情況的處理函數,只需要使用 login_manager 的 unauthorized_handler 裝飾器即可。

@login_manager.unauthorized_handler
def unauthorized():# do stuffreturn render_template("some template")

4、匿名用戶是怎么處理的?有哪些屬性?

在 flask-login 中,如果一個匿名用戶訪問站點,那么 current_user 對象會被設置成一個 AnonymousUserMixin 的對象,AnonymousUserMixin 對象有以下方法和屬性:

  • is_active?and is_authenticated are?False
  • is_anonymous?is?True
  • get_id()?returns?None

5、自定義匿名用戶Model:

如果你有需求自定義匿名用戶的 Model,那么你可以通過設置 login_manager 的 anonymous_user 屬性來實現,而賦值的對象只需是可調用對象(class 和 function都行)即可。

login_manager.anonymous_user = MyAnonymousUser

6、Flask-Login如何加載用戶的:

當一個請求過來的時候,如果 ctx.user 沒有值,那么 flask-login 就會使用 session 中 session['user_id'] 作為參數,調用 login_manager 中使用 user_loader 裝飾器設置的 callback 函數加載用戶,需要注意的是,如果指定的 user_id 無效,不應該拋出異常,而是應該返回 None。

登錄成功后,就可以使用 current_use r對象了,current_user 保存的就是當前用戶的信息,實質上是一個 User 對象,所以我們直接調用其屬性, 例如這里我們要給模板傳一個 username 的參數,就可以直接用 current_user.username

@login_manager.user_loader
def load_user(user_id):return User.get(user_id)

session['user_id'] 其實是在調用 login_in 函數之后自動設置的。

7、Flask-Login設置session過期時間:

在 Flask-Login 中,如果你不特殊處理的話,session 是在你關閉瀏覽器之后就失效的。也就是說每次重新打開頁面都是需要重新登錄的。如果你需要自己控制 session 的過期時間的話:

  • 首先需要設置 login_manager 的 session類型為永久的,
  • 然后再設置 session 的過期時間
#################### 配置文件 ####################
class Config:...PERMANENT_SESSION_LIFETIME = datetime.timedelta(minutes=5)#################### 登錄 ####################
def login():login_user(user)session.permanent = True  # 設置session永久有效  注意這個要設置在request里邊 即請求內部

同時,還需要注意的是 cookie 的默認有效期其實是 一年 的,所以,我們最好也設置一下:

login_manager.remember_cookie_duration=timedelta(days=1)

8、如何在同域名下的多個系統共享登錄狀態

這個需求可能在公司里面會比較常見,也就是說我們一個公司域名下面會有好多個子系統,但是這些子系統都是不同部門開發的,那么,我們如何在這不同系統間共享登錄狀態?也就是說,只要在某一個系統登錄了,在使用其他系統的時候也共享著登錄的狀態,不需要再次登錄,除非登錄失效。

Server-side Sessions with Redis

這個說明嘗試,也差不多是類似的解決方法。

9、使用Flask自帶的函數加密存儲密碼

# models.pyfrom werkzeug.security import generate_password_hash
from werkzeug.security import check_password_hash
from flask_login import UserMixin
import json
import uuid# define profile.json constant, the file is used to
# save user name and password_hash
PROFILE_FILE = "profiles.json"class User(UserMixin):def __init__(self, username):self.username = usernameself.id = self.get_id()@propertydef password(self):raise AttributeError('password is not a readable attribute')@password.setterdef password(self, password):"""save user name, id and password hash to json file"""self.password_hash = generate_password_hash(password)with open(PROFILE_FILE, 'w+') as f:try:profiles = json.load(f)except ValueError:profiles = {}profiles[self.username] = [self.password_hash,self.id]f.write(json.dumps(profiles))def verify_password(self, password):password_hash = self.get_password_hash()if password_hash is None:return Falsereturn check_password_hash(self.password_hash, password)def get_password_hash(self):"""try to get password hash from file.:return password_hash: if the there is corresponding user inthe file, return password hash.None: if there is no corresponding user, return None."""try:with open(PROFILE_FILE) as f:user_profiles = json.load(f)user_info = user_profiles.get(self.username, None)if user_info is not None:return user_info[0]except IOError:return Noneexcept ValueError:return Nonereturn Nonedef get_id(self):"""get user id from profile file, if not exist, it willgenerate a uuid for the user."""if self.username is not None:try:with open(PROFILE_FILE) as f:user_profiles = json.load(f)if self.username in user_profiles:return user_profiles[self.username][1]except IOError:passexcept ValueError:passreturn unicode(uuid.uuid4())@staticmethoddef get(user_id):"""try to return user_id corresponding User object.This method is used by load_user callback function"""if not user_id:return Nonetry:with open(PROFILE_FILE) as f:user_profiles = json.load(f)for user_name, profile in user_profiles.iteritems():if profile[1] == user_id:return User(user_name)except:return Nonereturn Non
  • User類需要繼承flask-login中的UserMixin類,用于實現相應的用戶會話管理。

  • 這里我們是直接存儲用戶信息到一個json文件"profiles.json"

  • 我們并不直接存儲密碼,而是存儲加密后的hash值,在這里我們使用了werkzeug.security包中的generate_password_hash函數來進行加密,由于此函數默認使用了sha1算法,并添加了長度為8的鹽值,所以還是相當安全的。一般用途的話也就夠用了。

  • 驗證password的時候,我們需要使用werkzeug.security包中的check_password_hash函數來驗證密碼

  • get_id是UserMixin類中就有的method,在這我們需要overwrite這個method。在json文件中沒有對應的user id時,可以使用uuid.uuid4()生成一個用戶唯一id

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/454043.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/454043.shtml
英文地址,請注明出處:http://en.pswp.cn/news/454043.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Zookeeper客戶端Curator使用詳解

http://www.jianshu.com/p/70151fc0ef5dZookeeper客戶端Curator使用詳解 簡介 Curator是Netflix公司開源的一套zookeeper客戶端框架,解決了很多Zookeeper客戶端非常底層的細節開發工作,包括連接重連、反復注冊Watcher和NodeExistsException異常等等。Pat…

python argparse nargs_Python | 使用argparse解析命令行參數

今天是Python專題第27篇文章,我們來聊聊Python當中的命令行參數工具argparse。命令行參數工具是我們非常常用的工具,比如當我們做實驗希望調節參數的時候,如果參數都是通過硬編碼寫在代碼當中的話,我們每次修改參數都需要修改對應…

Python 第三方模塊之 smtplib

1 python對SMTP的支持 SMTP(Simple Mail Transfer Protocol)是簡單傳輸協議,它是一組用于用于由源地址到目的地址的郵件傳輸規則。 python中對SMTP進行了簡單的封裝,可以發送純文本郵件、HTML郵件以及帶附件的郵件。兩個核心模塊…

Node.js 使用jQuery取得Nodejs http服務端返回的JSON對象示例

server.js代碼: // 內置http模塊,提供了http服務器和客戶端功能(path模塊也是內置模塊,而mime是附加模塊) var httprequire("http");// 創建服務器,創建HTTP服務器要調用http.createServer()函數&#xff0c…

linux下gdb單步調試

用 GDB調試程序 GDB 概述 ———— GDB 是 GNU開源組織發布的一個強大的 UNIX下的程序調試工具。或許,各位比較喜歡那種圖形界面方式的,像 VC、 BCB等 IDE的調試,但如果你是在 UNIX平臺下做軟件,你會發現 GDB這個調試工具有比 V…

svg 動畫_根據AI導出的SVG path制作SVG線條動畫

點擊右上方紅色按鈕關注“web秀”,讓你真正秀起來前言首先祝大家2019新年快樂,萬事大吉,豬事順利,闔家歡樂。前面文章SVG 線條動畫基礎入門知識學習到了基礎知識,現在來給大家講講如何制作SVG 制作復雜圖形線條動畫。假…

MySQL提示Truncated incorrect DOUBLE value解決方法

“Truncated incorrect DOUBLE value”的解決方法主要是這兩種: 1、修改了多個列的值而各列之間用逗號連接而不要用and 錯誤寫法示例:update tablename set col1value1 and col2value2 where col3value3;正確寫法示例:update ta…

一個完美的導航樹

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns"http://www.w3.org/1999/xhtml" ><head><title>無標題頁</title><…

自定義python框架_Python web 框架Sanic 學習: 自定義 Exception

Sanic 是一個和類Flask 的基于Python3.5的web框架&#xff0c;它使用了 Python3 異步特性&#xff0c;有遠超 flask 的性能。編寫 RESTful API 的時候&#xff0c;我們會定義特定的異常錯誤類型&#xff0c;比如我定義的錯誤返回值格式為&#xff1a;{"error_code": …

文字水印

using System.Threading.Tasks;using System.IO;using System.Drawing; public static int Shuy(string Sname,string fname) { try { Image image Image.FromFile(fname); Graphics gra Graphics.FromImage(image); String text Sname; Font font new Font("宋體&quo…

讀書筆記2013第3本:《無價》

《無價》這本書是過年前買的&#xff0c;網絡書店上寫著“老羅推薦”&#xff0c;想著好像是在老羅哪一年的演講里聽過這本書&#xff0c;在豆瓣上評分7.9。讀書是為了產生行動&#xff0c;讀書時要提的4個問題&#xff0c;1&#xff09;這本書主要在談些什么&#xff1f;2&…

Linux下的程序調試——GDB

無論是多么優秀的程序員&#xff0c;都難以保證自己在編寫代碼時不會出現任何錯誤&#xff0c;因此調試是軟件開發過程中的一個必不可少的 組成部分。當程序完成編譯之后&#xff0c;它很可能無法正常運行&#xff0c;或者會徹底崩潰&#xff0c;或者不能實現預期的功能。此時如…

圓錐曲線萬能弦長公式_2020高考數學50條秒殺型公式與方法

考試馬上就要到了&#xff0c;學姐整理了高考數學50條秒殺型公式和方法&#xff0c;希望能幫助考生們更好地攻克數學難關&#xff01;高考數學秒殺公式與方法一1&#xff0c;適用條件&#xff1a;[直線過焦點]&#xff0c;必有ecosA(x-1)/(x1)&#xff0c;其中A為直線與焦點所在…

Python 內置模塊之 logging

日志的級別和適用情況 級別適用情況DEBUG詳細信息&#xff0c;通常只在診斷問題時對其感興趣INFO確認工作正常WARNING表示發生了意料之外的事或者在不遠的將來會有問題&#xff08;比如磁盤空間低&#xff09;。軟件依然正常工作ERROR由于一個更加嚴重的問題&#xff0c;軟件不…

Memory barrier

待續 Memory barrier,是一種屏障和一類指令&#xff0c;在執行這個屏障指令前后&#xff0c;CPU或者編譯器在內存操作上強制一個約束序列。CPU使用性能優化器可以導致執行代碼的無序。在單一線程執行中&#xff0c;重排序內存操作通常不會被注意。但是在并行編程或者設備驅動中…

數據結構與算法 Python語言描述 筆記

數據結構 線性表包括順序表和鏈表&#xff0c;python的list是順序表&#xff0c;鏈表一般在動態語言中不會使用。不過鏈表還是會出現在各種算法題中。 鏈表 link list 單鏈表 逆轉鏈表&#xff1a; leetcode 206雙鏈表循環單鏈表字符串 string 有一個重要的點就是字符串的匹配問…

Flask 跨域問題

一、什么是跨域 跨域是指&#xff1a;瀏覽器A從服務器B獲取的靜態資源&#xff0c;包括Html、Css、Js&#xff0c;然后在Js中通過Ajax訪問C服務器的靜態資源或請求。即&#xff1a;瀏覽器A從B服務器拿的資源&#xff0c;資源中想訪問服務器C的資源。 同源策略是指&#xff1a;…

Hibernate 中配置屬性詳解(hibernate.properties)

轉自&#xff1a;https://blog.csdn.net/shudaqi2010/article/details/70324843 Hibernate能在各種不同環境下工作而設計的, 因此存在著大量的配置參數。多數配置參數都 有比較直觀的默認值, 并有隨 Hibernate一同分發的配置樣例hibernate.properties 來展示各種配置選項。 所需…

1.3 使用電腦測試MC20的電話語音功能

需要準備的硬件 MC20開發板 1個https://item.taobao.com/item.htm?id562661881042GSM/GPRS天線 1根https://item.taobao.com/item.htm?id531979567261IPEX接口轉SMA接口轉接線 1根https://item.taobao.com/item.htm?id531979903836GPS有源天線 1根https://item.taobao.com/i…

前端之 AJAX

AJAX參數詳細列表 參數名類型描述urlString(默認: 當前頁地址) 發送請求的地址。typeString(默認: "GET") 請求方式 ("POST" 、 "GET")。注意&#xff1a;其它 HTTP 請求方法&#xff0c;如 PUT 和 DELETE &#xff0c;但僅部分瀏覽器支持。tim…