軟件開(kāi)發(fā)中,安全是永恒的話題,F(xiàn)astAPI作為一個(gè)優(yōu)秀的Python Web開(kāi)發(fā)框架,為用戶提供了多種工具,幫助用戶以標(biāo)準(zhǔn)的方式輕松快速地解決軟件開(kāi)發(fā)中的安全性。
創(chuàng)新互聯(lián)網(wǎng)站建設(shè)公司一直秉承“誠(chéng)信做人,踏實(shí)做事”的原則,不欺瞞客戶,是我們最起碼的底線! 以服務(wù)為基礎(chǔ),以質(zhì)量求生存,以技術(shù)求發(fā)展,成交一個(gè)客戶多一個(gè)朋友!專注中小微企業(yè)官網(wǎng)定制,成都網(wǎng)站建設(shè)、成都網(wǎng)站制作,塑造企業(yè)網(wǎng)絡(luò)形象打造互聯(lián)網(wǎng)企業(yè)效應(yīng)。
FastAPI 的 fastapi.security 模塊中為各種安全方案提供了一些工具,這些工具簡(jiǎn)化了這些安全機(jī)制的使用方法。
FastAPI提供的OAuth2PasswordBearer是使用 OAuth2的密碼授權(quán)模式的Bearer Token(不記名 token) 。創(chuàng)建OAuth2PasswordBearer 實(shí)例需要接收URL作為參數(shù)。
客戶端會(huì)向該 URL 通過(guò)表單的格式發(fā)送 username 和 password 參數(shù),然后得到一個(gè) token 值;OAuth2PasswordBearer 并不會(huì)創(chuàng)建相應(yīng)的 URL 路徑操作,只是指明了客戶端用來(lái)獲取 token 的目標(biāo) URL。
代碼示例:
在上面的代碼中, tokenUrl="token"指的token是相對(duì) URL 。
此時(shí)訪問(wèn),其返回結(jié)果:
上面的結(jié)果表明:訪問(wèn)的內(nèi)容以及被保護(hù),必須經(jīng)過(guò)授權(quán)后才可以訪問(wèn)。
當(dāng)獲取到表單數(shù)據(jù)后,需要進(jìn)行密碼校驗(yàn),一般情況下,我們都會(huì)考慮使用哈希密碼,PassLib 是一個(gè)用于處理哈希密碼的非常好的 Python 包,它支持許多安全哈希算法以及配合算法使用的實(shí)用程序。
具體passlib的使用方法可以查看其文檔
下面的代碼示例在上面代碼的基礎(chǔ)上增加用戶登錄及Token驗(yàn)證
啟動(dòng)應(yīng)用并執(zhí)行請(qǐng)求:
測(cè)試無(wú)效登錄:
測(cè)試正常登錄:
返回token,在Headers中使用token訪問(wèn):
修改token后請(qǐng)求:
上面的代碼如果去掉 await verify_token(token) 行,則:
curl -H "Authorization:Bearer u000010007" -i
會(huì)得到返回結(jié)果,原因是默認(rèn)情況下,OAuth2PasswordBearer只負(fù)責(zé)請(qǐng)求頭中是否具有Authorization:Bearer,如果有就會(huì)執(zhí)行相應(yīng)的請(qǐng)求,所以,為了驗(yàn)證Token的正確性,需要每個(gè)方法都執(zhí)行相應(yīng)的驗(yàn)證代碼。
本例只作為例子,在實(shí)際開(kāi)發(fā)中不會(huì)直接拿用戶ID作為Token,為了提高系統(tǒng)的安全性,需要使用 JWT。下面我們就介紹 JWT。
JWT是一個(gè)將 JSON 對(duì)象編碼為密集且沒(méi)有空格的長(zhǎng)字符串的標(biāo)準(zhǔn)。 具體學(xué)習(xí)和了解 JWT,請(qǐng)參考 。
需要提到的主要是 JWT中的sub,JWT 的規(guī)范中有一個(gè) sub 鍵,值為該令牌的主題。使用它并不是必須的,但這是我們放置用戶標(biāo)識(shí)的地方,所以一般情況下,我們?cè)趕ub中存放用戶ID, 為了避免 ID 沖突,當(dāng)為創(chuàng)建 JWT 令牌時(shí),可以在 sub 鍵的值前加上前綴,例如 username:、userid:等。
在 Python 中生成和校驗(yàn) JWT 令牌 ,可以使用PyJWT,也可以使用 python-jose 。我們?cè)诒纠惺褂?python-jose 來(lái)編寫代碼。
使用:
使用 JWT,需要在系統(tǒng)中添加一個(gè)SECRET_KEY變量,用于生成令牌,如:
以下代碼在上面代碼的基礎(chǔ)上使用 JWT 令牌。
與前面的代碼差別之處:
1.生成Token的函數(shù):build_access_token
2.校驗(yàn)Token的函數(shù):verify_token
3.登錄函數(shù):login
請(qǐng)求測(cè)試:
登錄:
令牌訪問(wèn):
錯(cuò)誤的令牌訪問(wèn):
在大部分應(yīng)用程序中,當(dāng)用戶訪問(wèn)某個(gè)接口API的時(shí)候,都需要明確訪問(wèn)者的身份,所以在應(yīng)用程序中需要隨時(shí)獲取當(dāng)前用戶,由于在 JWT 令牌的 sub 字段中已經(jīng)保存了用戶信息,所以獲取當(dāng)前用戶只需要對(duì)令牌解碼即可。
在上面的代碼的基礎(chǔ)上,增加兩個(gè)函數(shù),代碼如下:
請(qǐng)求測(cè)試:
以上,我們完成了一個(gè)簡(jiǎn)單的安全性示例,F(xiàn)astAPI提供的安全性框架幫助我們節(jié)約了很多代碼,但在實(shí)際開(kāi)發(fā)中,我們常常使用微服務(wù)的方式來(lái)開(kāi)發(fā),對(duì)于鑒權(quán)最好設(shè)計(jì)獨(dú)立的微服務(wù)進(jìn)行處理。后面我們會(huì)展示一個(gè)采用FastAPI開(kāi)發(fā)的鑒權(quán)微服務(wù),以便在此基礎(chǔ)上進(jìn)行業(yè)務(wù)系統(tǒng)的開(kāi)發(fā)。
簡(jiǎn)答來(lái)說(shuō):通過(guò)外部的一個(gè)變量
T=fasle
def regist():
""" 注冊(cè)"""
print “注冊(cè)”
T=true
def login():
""" 登陸"""
if not T:
print "先注冊(cè)"
return
print “登陸成功”
def logout():
""" 注銷"""
T=fasle
一般的話
注冊(cè)后都會(huì)在數(shù)據(jù)庫(kù)中 記錄注冊(cè)信息
登陸的時(shí)候 先去到數(shù)據(jù)庫(kù)中查看是否有 沒(méi)有返回空 有的話返回注冊(cè)信息 ,比如登陸密碼 用于下步的密碼核對(duì)
單點(diǎn)登錄跟登錄其實(shí)差不多,理解了登錄也可以搞出單點(diǎn)登錄
回顧
在前面的系列章節(jié)中,我們創(chuàng)建了一個(gè)數(shù)據(jù)庫(kù)并且學(xué)著用用戶和郵件來(lái)填充,但是到現(xiàn)在我們還沒(méi)能夠植入到我們的程序中。 兩章之前,我們已經(jīng)看到怎么去創(chuàng)建網(wǎng)絡(luò)表單并且留下了一個(gè)實(shí)現(xiàn)完全的登陸表單。
在這篇文章中,我們將基于我門所學(xué)的網(wǎng)絡(luò)表單和數(shù)據(jù)庫(kù)來(lái)構(gòu)建并實(shí)現(xiàn)我們自己的用戶登錄系統(tǒng)。教程的最后我們小程序會(huì)實(shí)現(xiàn)新用戶注冊(cè),登陸和退出的功能。
為了能跟上這章節(jié),你需要前一章節(jié)最后部分,我們留下的微博程序。請(qǐng)確保你的程序已經(jīng)正確安裝和運(yùn)行。
在前面的章節(jié),我們開(kāi)始配置我們將要用到的Flask擴(kuò)展。為了登錄系統(tǒng),我們將使用兩個(gè)擴(kuò)展,Flask-Login 和 Flask-OpenID. 配置如下所示 (fileapp\__init__.py):
import os
from flaskext.login import LoginManager
from flaskext.openid import OpenID
from config import basedir
lm = LoginManager()
lm.setup_app(app)
oid = OpenID(app, os.path.join(basedir, 'tmp'))
Flask-OpenID 擴(kuò)展為了可以存儲(chǔ)臨時(shí)文件,需要一個(gè)臨時(shí)文件夾路徑。為此,我們提供了它的位置。
重訪我們的用戶模型
Flask-Login擴(kuò)展需要在我們的User類里實(shí)現(xiàn)一些方法。除了這些方法以外,類沒(méi)有被要求實(shí)現(xiàn)其它方法。
下面是我們的User類 (fileapp/models.py):
class User(db.Model):
id = db.Column(db.Integer, primary_key = True)
nickname = db.Column(db.String(64), unique = True)
email = db.Column(db.String(120), unique = True)
role = db.Column(db.SmallInteger, default = ROLE_USER)
posts = db.relationship('Post', backref = 'author', lazy = 'dynamic')
def is_authenticated(self):
return True
def is_active(self):
return True
def is_anonymous(self):
return False
def get_id(self):
return unicode(self.id)
def __repr__(self):
return 'User %r' % (self.name)
is_authenticated方法是一個(gè)誤導(dǎo)性的名字的方法,通常這個(gè)方法應(yīng)該返回True,除非對(duì)象代表一個(gè)由于某種原因沒(méi)有被認(rèn)證的用戶。
is_active方法應(yīng)該為用戶返回True除非用戶不是激活的,例如,他們已經(jīng)被禁了。
is_anonymous方法應(yīng)該為那些不被獲準(zhǔn)登錄的用戶返回True。
最后,get_id方法為用戶返回唯一的unicode標(biāo)識(shí)符。我們用數(shù)據(jù)庫(kù)層生成唯一的id。
用戶加載回調(diào)
現(xiàn)在我們通過(guò)使用Flask-Login和Flask-OpenID擴(kuò)展來(lái)實(shí)現(xiàn)登錄系統(tǒng)
首先,我們需要寫一個(gè)方法從數(shù)據(jù)庫(kù)加載到一個(gè)用戶。這個(gè)方法會(huì)被Flask-Login使用(fileapp/views.py):
@lm.user_loader
def load_user(id):
return User.query.get(int(id))
記住Flask-Login里的user id一直是unicode類型的,所以在我們把id傳遞給Flask-SQLAlchemy時(shí),有必要把它轉(zhuǎn)化成integer類型。
登錄視圖函數(shù)
接下來(lái)我們要更新登錄視圖函數(shù)(fileapp/views.py):
from flask import render_template, flash, redirect, session, url_for, request, g
from flaskext.login import login_user, logout_user, current_user, login_required
from app import app, db, lm, oid
from forms import LoginForm
from models import User, ROLE_USER, ROLE_ADMIN
@app.route('/login', methods = ['GET', 'POST'])
@oid.loginhandler
def login():
if g.user is not None and g.user.is_authenticated():
return redirect(url_for('index'))
form = LoginForm()
if form.validate_on_submit():
session['remember_me'] = form.remember_me.data
return oid.try_login(form.openid.data, ask_for = ['nickname', 'email'])
return render_template('login.html',
title = 'Sign In',
form = form,
providers = app.config['OPENID_PROVIDERS'])
注意到我們導(dǎo)入了一些新的模塊,其中有些后面會(huì)用到。
跟上個(gè)版本的變化很小。我們給視圖函數(shù)添加了一個(gè)新的裝飾器:oid.loginhandler。它告訴Flask-OpenID這是我們的登錄視圖函數(shù)。
在方法體的開(kāi)頭,我們檢測(cè)是是否用戶是已經(jīng)經(jīng)過(guò)登錄認(rèn)證的,如果是就重定向到index頁(yè)面。這兒的思路是如果一個(gè)用戶已經(jīng)登錄了,那么我們不會(huì)讓它做二次登錄。
全局變量g是Flask設(shè)置的,在一個(gè)request生命周期中,用來(lái)存儲(chǔ)和共享數(shù)據(jù)的變量。所以我猜你已經(jīng)想到了,我們將把已經(jīng)登錄的用戶放到g變量里。
我們?cè)谡{(diào)用redirect()時(shí)使用的url_for()方法是Flask定義的從給定的view方法獲取url。如果你想重定向到index頁(yè)面,你h很可能使用redirect('/index'),但是我們有很好的理由讓Flask為你構(gòu)造url。
當(dāng)我們從登錄表單得到返回?cái)?shù)據(jù),接下來(lái)要運(yùn)行的代碼也是新寫的。這兒我們做兩件事。首先我們保存remember_me的布爾值到Flask的session中,別和Flask-SQLAlchemy的db.session混淆了。我們已經(jīng)知道在一個(gè)request的生命周期中用Flask的g對(duì)象來(lái)保存和共享數(shù)據(jù)。沿著這條線路Flask的session提供了更多,更復(fù)雜的服務(wù)。一旦數(shù)據(jù)被保存到session中,它將在同一客戶端發(fā)起的這次請(qǐng)求和這次以后的請(qǐng)求中永存而不會(huì)消亡。數(shù)據(jù)將保持在session中直到被明確的移除。為了做到這些,F(xiàn)lask為每個(gè)客戶端建立各自的session。
下面的oid.try_login是通過(guò)Flask-OpenID來(lái)執(zhí)行用戶認(rèn)證。這個(gè)方法有兩個(gè)參數(shù),web表單提供的openid和OpenID provider提供的我們想要的list數(shù)據(jù)項(xiàng)。由于我們定義了包含nickname和email的User類,所以我們要從找nickname和email這些項(xiàng)。
基于OpenID的認(rèn)證是異步的。如果認(rèn)證成功,F(xiàn)lask-OpenID將調(diào)用有由oid.after_login裝飾器注冊(cè)的方法。如果認(rèn)證失敗那么用戶會(huì)被重定向到login頁(yè)面。
Flask-OpenID登錄回調(diào)
這是我們實(shí)現(xiàn)的after_login方法(app/views.py)
@oid.after_login
def after_login(resp):
if resp.email is None or resp.email == "":
flash('Invalid login. Please try again.')
redirect(url_for('login'))
user = User.query.filter_by(email = resp.email).first()
if user is None:
nickname = resp.nickname
if nickname is None or nickname == "":
nickname = resp.email.split('@')[0]
user = User(nickname = nickname, email = resp.email, role = ROLE_USER)
db.session.add(user)
db.session.commit()
remember_me = False
if 'remember_me' in session:
remember_me = session['remember_me']
session.pop('remember_me', None)
login_user(user, remember = remember_me)
return redirect(request.args.get('next') or url_for('index'))
傳給after_login方法的resp參數(shù)包含了OpenID provider返回的一些信息。
第一個(gè)if聲明僅僅是為了驗(yàn)證。我們要求一個(gè)有效的email,所以一個(gè)沒(méi)有沒(méi)提供的email我們是沒(méi)法讓他登錄的。
接下來(lái),我們將根據(jù)email查找數(shù)據(jù)庫(kù)。如果email沒(méi)有被找到我們就認(rèn)為這是一個(gè)新的用戶,所以我們將在數(shù)據(jù)庫(kù)中增加一個(gè)新用戶,做法就像我們從之前章節(jié)學(xué)到的一樣。注意我們沒(méi)有處理nickname,因?yàn)橐恍㎡penID provider并沒(méi)有包含這個(gè)信息。
做完這些我們將從Flask session中獲取remember_me的值,如果它存在,那它是我們之前在login view方法中保存到session中的boolean類型的值。
然后我們調(diào)用Flask-Login的login_user方法,來(lái)注冊(cè)這個(gè)有效的登錄。
最后,在最后一行我們重定向到下一個(gè)頁(yè)面,或者如果在request請(qǐng)求中沒(méi)有提供下個(gè)頁(yè)面時(shí),我們將重定向到index頁(yè)面。
跳轉(zhuǎn)到下一頁(yè)的這個(gè)概念很簡(jiǎn)單。比方說(shuō)我們需要你登錄才能導(dǎo)航到一個(gè)頁(yè)面,但你現(xiàn)在并未登錄。在Flask-Login中你可以通過(guò)login_required裝飾器來(lái)限定未登錄用戶。如果一個(gè)用戶想連接到一個(gè)限定的url,那么他將被自動(dòng)的重定向到login頁(yè)面。Flask-Login將保存最初的url作為下一個(gè)頁(yè)面,一旦登錄完成我們便跳轉(zhuǎn)到這個(gè)頁(yè)面。
做這個(gè)工作Flask-Login需要知道用戶當(dāng)前在那個(gè)頁(yè)面。我們可以在app的初始化組件里配置它(app/__init__.py):
lm = LoginManager()
lm.setup_app(app)
lm.login_view = 'login'
全局變量g.user
如果你注意力很集中,那么你應(yīng)該記得在login view方法中我們通過(guò)檢查g.user來(lái)判斷一個(gè)用戶是否登錄了。為了實(shí)現(xiàn)這個(gè)我們將使用Flask提供的before_request事件。任何一個(gè)被before_request裝飾器裝飾的方法將會(huì)在每次request請(qǐng)求被收到時(shí)提前與view方法執(zhí)行。所以在這兒來(lái)設(shè)置我們的g.user變量(app/views.py):
@app.before_request
def before_request():
g.user = current_user
這就是它要做的一切,current_user全局變量是被Flask-Login設(shè)定的,所以我們只需要把它拷貝到更容易被訪問(wèn)的g變量就OK了。這樣,所有的請(qǐng)求都能訪問(wèn)這個(gè)登錄的用戶,甚至于內(nèi)部的模板。
index視圖
在之前的章節(jié)中我們用假代碼遺留了我們的index視圖,因?yàn)槟莻€(gè)時(shí)候我們系統(tǒng)里并沒(méi)有用戶和博客文章。現(xiàn)在我們有用戶了,所以,讓我們來(lái)完成它吧:
@app.route('/')
@app.route('/index')
@login_required
def index():
user = g.user
posts = [
{
'author': { 'nickname': 'John' },
'body': 'Beautiful day in Portland!'
},
{
'author': { 'nickname': 'Susan' },
'body': 'The Avengers movie was so cool!'
}
]
return render_template('index.html',
title = 'Home',
user = user,
posts = posts)
在這個(gè)方法中只有兩處變動(dòng)。首先,我們?cè)黾恿薼ogin_required裝飾器。這樣表明了這個(gè)頁(yè)面只有登錄用戶才能訪問(wèn)。
另一個(gè)改動(dòng)是把g.user傳給了模板,替換了之間的假對(duì)象。
現(xiàn)在可以運(yùn)行我們的應(yīng)用了。
當(dāng)我們連接到你將會(huì)看到登陸頁(yè)面。記著如果你通過(guò)OpenID登錄那么你必須使用你的提供者提供的OpenID URL。你可以下面URL中的任何一個(gè)OpenID provider來(lái)為你產(chǎn)生一個(gè)正確的URL。
作為登錄進(jìn)程的一部分,你將會(huì)被重定向到OpenID提供商的網(wǎng)站,你將在那兒認(rèn)證和授權(quán)你共享給我們應(yīng)用的一些信息(我們只需要email和nickname,放心,不會(huì)有任何密碼或者其他個(gè)人信息被曝光)。
一旦登錄完成你將作為已登錄用戶被帶到index頁(yè)面。
試試勾選remember_me復(fù)選框。有了這個(gè)選項(xiàng)當(dāng)你在瀏覽器關(guān)閉應(yīng)用后重新打開(kāi)時(shí),你還是已登錄狀態(tài)。
注銷登錄
我們已經(jīng)實(shí)現(xiàn)了登錄,現(xiàn)在是時(shí)候來(lái)實(shí)現(xiàn)注銷登錄了。
注銷登錄的方法灰常簡(jiǎn)單(file app/views.py):
@app.route('/logout')
def logout():
logout_user()
return redirect(url_for('index'))
但我們?cè)谀0逯羞€沒(méi)有注銷登錄的鏈接。我們將在base.html中的頂部導(dǎo)航欄添加這個(gè)鏈接(file app/templates/base.html):
html
head
{% if title %}
title{{title}} - microblog/title
{% else %}
titlemicroblog/title
{% endif %}
/head
body
divMicroblog:
a href="{{ url_for('index') }}"Home/a
{% if g.user.is_authenticated() %}
| a href="{{ url_for('logout') }}"Logout/a
{% endif %}
/div
hr
{% with messages = get_flashed_messages() %}
{% if messages %}
ul
{% for message in messages %}
li{{ message }} /li
{% endfor %}
/ul
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
/body
/html
這是多么多么簡(jiǎn)單啊,我們只需要檢查一下g.user中是否有一個(gè)有效的用戶,如果有我們就添加注銷鏈接。在我們的模板中我們?cè)僖淮问褂昧藆rl_for方法。
最后的話
我們現(xiàn)在有了一個(gè)全功能的用戶登錄系統(tǒng)。在下一章中,我們將創(chuàng)建用戶的個(gè)人資料頁(yè),并顯示用戶的頭像。
當(dāng)前名稱:python登錄函數(shù). python登錄程序
分享URL:http://m.newbst.com/article20/hjhsjo.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供移動(dòng)網(wǎng)站建設(shè)、關(guān)鍵詞優(yōu)化、網(wǎng)站維護(hù)、網(wǎng)站內(nèi)鏈、做網(wǎng)站、Google
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)