做律師網(wǎng)站的公司大二網(wǎng)絡(luò)營(yíng)銷實(shí)訓(xùn)報(bào)告
在之前的課程中,介紹過(guò) Flask-Login 框架,它是基于 Session 和 Cookie 技術(shù)來(lái)實(shí)現(xiàn)用戶授權(quán)和驗(yàn)證的,不過(guò) Session 有很多的局限性,這一節(jié)介紹一種基于 token 的驗(yàn)證方式 —— JWT (JSON Web Token),除了對(duì) JWT 的概念講解之外,還有在 Flask 中簡(jiǎn)單實(shí)踐
session 的局限性
基于 Session 的驗(yàn)證過(guò)程大體是:服務(wù)器端有一個(gè) Session 詞典,當(dāng)用戶驗(yàn)證登錄后,在詞典中為該用戶創(chuàng)建一個(gè) Session 對(duì)象,在響應(yīng)( response )中返回一個(gè) Session id,當(dāng)用戶下次請(qǐng)求時(shí),攜帶 Session id,服務(wù)器從 Session 詞典中可以恢復(fù)出 Session 對(duì)象,以完成用戶的驗(yàn)證,在用 Session id 從恢復(fù)出認(rèn)證實(shí)體。
從 Session 驗(yàn)證過(guò)程可以看出一些局限性:
-
服務(wù)器橫向擴(kuò)展很困難:因?yàn)?Session 只能存活在一個(gè)服務(wù)實(shí)例中,將用戶請(qǐng)求引導(dǎo)到其他服務(wù)器,將丟掉用戶的登錄狀態(tài)
-
攜帶信息量少,恢復(fù)會(huì)話信息比較耗時(shí):Session 認(rèn)證后,客戶端得到 Session ID, 服務(wù)器無(wú)法從 Session ID 中得到更多信息,需要從數(shù)據(jù)庫(kù)、文件系統(tǒng)或緩存中取得用戶信息,比較耗時(shí)
-
沒(méi)有統(tǒng)一標(biāo)準(zhǔn):Session 由各個(gè)服務(wù)器框架自己實(shí)現(xiàn),沒(méi)有統(tǒng)一標(biāo)準(zhǔn),存在應(yīng)用擴(kuò)展困難的問(wèn)題,特別加密方式,五花八門(mén),有很大的安全隱患
token 簡(jiǎn)介
為了解決 Session 的問(wèn)題,有了 token 的驗(yàn)證方式。
token 可以理解成票據(jù),或者憑證,當(dāng)用戶得到服務(wù)器的認(rèn)證后,由服務(wù)器頒發(fā),在之后的請(qǐng)求時(shí)攜帶,免去頻繁登錄。
token 不同于 Session 的地方:
-
可以獨(dú)立于具體的服務(wù)器框架生成和校驗(yàn)
-
可以攜帶更多的信息,避免對(duì)持久層的查詢操作
-
基于標(biāo)準(zhǔn)的算法可以由不同的節(jié)點(diǎn)完成驗(yàn)證
為了利用好 token 的驗(yàn)證機(jī)制,IEIT (互聯(lián)網(wǎng)工程任務(wù)組),制定了基于 JSON 數(shù)據(jù)結(jié)構(gòu)的網(wǎng)絡(luò)認(rèn)證方式 JWA(JSON Web Algorithms),還針對(duì)不同應(yīng)用場(chǎng)景提出了具體協(xié)議,如 JWS、JWE、JWK 等,他們可以統(tǒng)稱為 JWT,即 Javascript Web Token。
理解 JWA
JWA 的全稱是 JSON Web Algorithms
JSON 是 Javascript 的語(yǔ)言的文本對(duì)象表示法,是一種獨(dú)立語(yǔ)言環(huán)境的數(shù)據(jù)結(jié)構(gòu)表示,可以用網(wǎng)絡(luò)數(shù)據(jù)傳輸,在前面 RESTful 章節(jié)中,對(duì) API 調(diào)用的返回?cái)?shù)據(jù)格式就是 JSON。
Algorithms 本義是算法的意思,這里特指加密算法,也就是用 JSON 表示的數(shù)據(jù),經(jīng)過(guò)加密后在在服務(wù)器端和客戶段之間傳輸。
有了數(shù)據(jù)結(jié)構(gòu)和加密算法的基礎(chǔ),根據(jù)不同的應(yīng)用場(chǎng)景,定義出了具體實(shí)現(xiàn):
-
JWS(JSON Web Signature)對(duì)數(shù)據(jù)進(jìn)行簽名的,用于防止數(shù)據(jù)被篡改,傳輸不敏感數(shù)據(jù)的情況
-
JWE(JSON Web Encryption)對(duì)數(shù)據(jù)做了加密的,用于傳輸敏感數(shù)據(jù),具有更好的安全性
-
JWK(JSON Web Key)是通過(guò)密鑰對(duì)數(shù)據(jù)進(jìn)行加密的方法,規(guī)定了相應(yīng)的加密算法
JWT(JSON Web Token)上面 JWS、JWE 和 JWK 的總稱。
JWT 簡(jiǎn)介
JWT Wiki 上的定義是:
JSON Web Token is an Internet standard for creating JSON-based access tokens that assert some number of claims.
大致意思是,JWT 是用基于 JSON 數(shù)據(jù)結(jié)構(gòu)的生成包含了一些權(quán)限聲明的網(wǎng)絡(luò)訪問(wèn)憑證的網(wǎng)絡(luò)標(biāo)準(zhǔn)
數(shù)據(jù)結(jié)構(gòu)
JWT 由?Header
、Payload
?和Signature
,三部分組成,像這樣的形式:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJpc3MiOiJBdXRobGliIiwic3ViIjoiMTIzIiwibmFtZSI6ImJvYiJ9.
cBo6e7Uss5__16mlqZECjHJSKJDdyisevDP5cUGvJms
換行符只是為了展示用,實(shí)際 token 中不包括換行符
Header
用于指定采用的加密算法,以及 JWT 采用的形式類型,例如:???????
{
"alg" : "HS256",
"typ" : "JWT"
}
-
alg
?指定前面所用的算法,默認(rèn)為 HmacSHA256 簡(jiǎn)寫(xiě)為 HS256,還有 HS384、RS256 等 -
typ
?是指令牌的類型,JWT 令牌的類型為?JWT
Payload
用于攜帶一些信息,例如用戶名,過(guò)期時(shí)間 等等,例如:???????
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
JWT 標(biāo)準(zhǔn)定義了 7 個(gè)字段:
字段 | 說(shuō)明 |
---|---|
iss | (issuer):簽發(fā)人 |
exp | (expiration time):過(guò)期時(shí)間 |
sub | (subject):主題 |
aud | (audience):受眾 |
nbf | (Not Before):生效時(shí)間 |
iat | (Issued At):簽發(fā)時(shí)間 |
jti | (JWT ID):編號(hào) |
這些字段有實(shí)現(xiàn)這自由選取,也可以加入其他自定義字段
Signature
首先,需要指定一個(gè)密鑰(secret)。密鑰很重要,需要嚴(yán)格保密
然后,使用 Header 里面指定的簽名算法(默認(rèn)是 HMAC SHA256),按照下面的公式產(chǎn)生簽名:???????
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
即先將?header
?和?payload
?分別做 base64url 編碼, 然后用?.
?將他們連接成一個(gè)字符串,用加密算法,使用密鑰?secret
, 得到的加密結(jié)果就算簽名
Base64URL 編碼字符集是 Base64 字符集的子集
=
?被省略、+
?替換成?-
,/
?替換成_
因?yàn)?token 可能通過(guò) URL 進(jìn)行傳輸,而=
、+
、/
?在 URL 中有特殊含義
驗(yàn)證
當(dāng)客戶端發(fā)送請(qǐng)求時(shí)將 token 送到服務(wù)器端,可以用和簽名同樣的方式,重新計(jì)算一次簽名,如果和客戶端送過(guò)來(lái)的簽名一致,說(shuō)明 token 沒(méi)有被篡改,如果不一致,說(shuō)明 token 已被篡改,不安全了。
由此可見(jiàn),用于做簽名的密鑰 secret 很重要,一旦泄漏,將無(wú)法鑒別 token 的真?zhèn)?/strong>
JWT 應(yīng)用
關(guān)于 Python 的 JWT 實(shí)現(xiàn)不止一個(gè),不同的庫(kù),不同的實(shí)現(xiàn)方式層出不窮,今天要講解的是 Python 的 Authlib 庫(kù),它是一個(gè)大而全的 Python Web 驗(yàn)證庫(kù)支持多種 Python 框架
Authlib 的 JWT
Authlib 是構(gòu)建 OAuth 和 OpenID 安全連接服務(wù)器的終極 Python 庫(kù),包括了 JWS, JWE, JWK, JWA, JWT
Authlib 功能強(qiáng)大而豐富,今天我們只了解他的 JWT 部分,之后在介紹基于第三方認(rèn)證的 OAuth 技術(shù)時(shí)還會(huì)進(jìn)一步講解
安裝
使用 pip 安裝
pip install Authlib
如果一切正常,可以導(dǎo)入 Authlib 模板,例如,引入 jwt :???????
>>> from authlib.jose import jwt
>>>
小試牛刀
JWT 是服務(wù)器端的機(jī)制,所以可以在命令行中做測(cè)試
生成 token???????
>>> from authlib.jose import jwt
>>> header = {'alg': 'HS256'}
>>> payload = {'iss': 'Authlib', 'sub': '123', 'name': 'bob'}
>>> secret = '123abc.'
>>> token = jwt.encode(header, payload, secret)
>>> print(token)
b'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJpc3MiOiJBdXRobGliIiwic3ViIjoiMTIzIiwibmFtZSI6ImJvYiJ9.
cBo6e7Uss5__16mlqZECjHJSKJDdyisevDP5cUGvJms'
-
導(dǎo)入 jwt 模塊
-
定義?
header
,并且設(shè)置簽名算法為?HS256
-
定義?
payload
,作為傳輸信息 -
定義?
secret
,注意這里只是方便演示,實(shí)際項(xiàng)目中最好是隨機(jī)生成,并妥善保存 -
使用 jwt 的?
encode
?方法,生成 token,encode
?方法一次性實(shí)現(xiàn)了所有關(guān)于 JWT 協(xié)議的定義 -
打印出 token,可見(jiàn),被?
.
?分隔為三部分,前兩部分是?header
?和?payload
的 Base64Url 編碼,最后一部分是 簽名
解碼 token
接上面的環(huán)境:???????
>>> claims = jwt.decode(token, secret)
>>> print(claims)
{'iss': 'Authlib', 'sub': '123', 'name': 'bob'}
>>> print(claims.header)
{'alg': 'HS256', 'typ': 'JWT'}
>>> claims.validate()
>>>
-
用 jwt 模塊的?
decode
?方法,利用?secret
?對(duì)?token
?進(jìn)行解碼,如果簽名正確,就會(huì)得到解碼內(nèi)容,解碼對(duì)象是?authlib.jose.JWTClaims
?類的實(shí)例 -
打印出解碼內(nèi)容,可以看到和生成 token 時(shí)的?
payload
?內(nèi)容一致 -
打印出?
header
,可以看到?typ
?為JWT
,即使用默認(rèn)值 -
validate
?方法用于檢驗(yàn) token 的有效性,比如:是否過(guò)期、主題是否一致,是否沒(méi)到生效時(shí)間等等,也可以針對(duì)每種情況單獨(dú)做驗(yàn)證,例如validate_exp
?可用檢驗(yàn)是否過(guò)期
雖然 JWT 理論很繁瑣,但 Authlib 庫(kù)提供了簡(jiǎn)潔的方法,讓開(kāi)發(fā)應(yīng)用變得更高效
與客戶端交互
JWT 之所有流行,有個(gè)重要原因是可以支持多種客戶端,例如 瀏覽器和 app,JWT 標(biāo)準(zhǔn)規(guī)定,一般情況下,客戶端需要將 token 放在 Http 請(qǐng)求的 Header 中的 Authorization 字段中,舉個(gè)例子:???????
GET /resource HTTP/1.1
Host: server.example.com
Authorization: Bearer mF_9.B5f-4.1JqM
-
用 GET 方式請(qǐng)求?
/resource
?,在 Header 中添加了?Authorization
?字段 -
不能直接將 token 作為?
Authorization
的值,必須有類型聲明,這里是Bearer
Bearer
?表示這個(gè) token 是由認(rèn)證服務(wù)器生成的,用來(lái)做身份識(shí)別的,除此之外,IEIT 還定義了其他 認(rèn)證類型,如?Bisic
,?Digest
,可以簡(jiǎn)單理解成?Bearer
?就是 JWT 的認(rèn)證類型
除了通過(guò) Http Header 類攜帶 token 之外,還可以通過(guò) POST 請(qǐng)求主體,以及 URL 中的 querystring 來(lái)向服務(wù)器發(fā)送 token,這兩種情況下,需要使用 access_token 字段來(lái)表示 token
JWT 標(biāo)準(zhǔn)建議使用 Header 方式,除非 Header 無(wú)法使用時(shí)才考慮其他方式
Flask JWT
Authlib 主要的用途在打造一個(gè) OAuth 應(yīng)用,對(duì)于單獨(dú)做 JWT 的實(shí)踐有些麻煩,因此我們用 flask-jwt 框架,做 JWT 的實(shí)踐。
flask-jwt 和之前講述的 flask-login 用法很像,是基于 JWT 的認(rèn)證的框架,提供和很多方便實(shí)踐的特性
安裝 flask-jwt
pip install Flask-JWT
創(chuàng)建應(yīng)用
為了簡(jiǎn)單,將所有代碼放在 app.py 中:???????
from flask import Flask
from flask_jwt import JWT, jwt_required, current_identity
from werkzeug.security import safe_str_cmp
# User 類,用于模擬用戶實(shí)體
class User(object):
def __init__(self, id, username, password):
self.id = id
self.username = username
self.password = password
def __str__(self):
return "User(id='%s')" % self.id
# User 實(shí)體集合,用于模擬用戶對(duì)象的緩存
users = [
User(1, 'user1', 'abcxyz'),
User(2, 'user2', 'abcxyz'),
]
username_table = {u.username: u for u in users}
userid_table = {u.id: u for u in users}
# 獲取認(rèn)證的回調(diào)函數(shù),從 request 中得到登錄憑證,返回憑證所代表的 用戶實(shí)體
def authenticate(username, password):
user = username_table.get(username, None)
if user and safe_str_cmp(user.password.encode('utf-8'), password.encode('utf-8')):
return user
# 通過(guò) token 獲得認(rèn)證主體的回調(diào)函數(shù)
def identity(payload):
user_id = payload['identity']
return userid_table.get(user_id, None)
app = Flask(__name__)
app.debug = True
app.config['SECRET_KEY'] = 'super-secret'
jwt = JWT(app, authenticate, identity) # 用 JWT 初始化應(yīng)用
@app.route('/protected', methods= ["GET", "POST"]) # 定義一個(gè) endpoint
@jwt_required() # 聲明需要 token 才能訪問(wèn)
def protected():
return '%s' % current_identity # 驗(yàn)證通過(guò)返回 認(rèn)證主體
if __name__ == '__main__':
app.run()
運(yùn)行:???????
$ python app.py
* Serving Flask app "app" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 566-326-511
獲取 access_token
flask-jwt 默認(rèn)的獲取 token 的路由是/auth
,請(qǐng)求方式是 POST,用 JSON 傳送用戶名密碼給服務(wù)器,例如:???????
$ curl -X POST -H "Content-Type: application/json" localhost:5000/auth -d '{"username":"user1","password":"abcxyz"}'
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
eyJleHAiOjE...<省略>...VudGl0eSI6MX0.
M-shnDPAVdu...<省略>...LaH1EMIbrWjPto"
}
如果登錄憑證正確,則返回 access_token,可以看到被?.
?分隔成三部分,即 JWT 的結(jié)構(gòu)
使用 access_token
flask-jwt 默認(rèn)通過(guò) Header 傳送 token,為了和 OAuth 生成的 JWT 做區(qū)分,默認(rèn)使用JWT
?作為 token 的類型,例如,用上面生成的 JWT 請(qǐng)求?/protected
:???????
curl -H "Authorization: jwt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE...<省略>...VudGl0eSI6MX0.M-shnDPAVdu...<省略>...LaH1EMIbrWjPto" localhost:5000/protected
User(id='1')
如果 token 有效,則返回 token 對(duì)應(yīng)的認(rèn)證實(shí)體,這個(gè)例子中打印出了 user 實(shí)體
總結(jié)
本節(jié)課程講解了基于 token 驗(yàn)證的 JWT,使用 Authlib 庫(kù)對(duì) JWT 做了實(shí)踐練習(xí),期望能幫助您更好的理解 JWT,最后通過(guò) flask-jwt 模塊,實(shí)踐了 JWT 的驗(yàn)證方式,和使用方式。在后續(xù)的課程中還會(huì)對(duì)目前流行的第三方認(rèn)證框架 OAuth 做介紹,敬請(qǐng)期待。