在以前的課程中,介紹過 Flask-Login 框架,它是基於 Session 和 Cookie 技術來實現用戶受權和驗證的,不過 Session 有不少的侷限性,這一節介紹一種基於 token 的驗證方式 —— JWT (JSON Web Token),除了對 JWT 的概念講解以外,還有在 Flask 中簡單實踐html
基於 Session 的驗證過程大致是:服務器端有一個 Session 詞典,當用戶驗證登陸後,在詞典中爲該用戶建立一個 Session 對象,在響應( response )中返回一個 Session id,當用戶下次請求時,攜帶 Session id,服務器從 Session 詞典中能夠恢復出 Session 對象,以完成用戶的驗證,在用 Session id 從恢復出認證明體。python
從 Session 驗證過程能夠看出一些侷限性:web
爲了解決 Session 的問題,有了 token 的驗證方式。算法
token 能夠理解成票據,或者憑證,當用戶獲得服務器的認證後,由服務器頒發,在以後的請求時攜帶,免去頻繁登陸。數據庫
token 不一樣於 Session 的地方:json
爲了利用好 token 的驗證機制,IEIT (互聯網工程任務組),制定了基於 JSON 數據結構的網絡認證方式 JWA(JSON Web Algorithms),還針對不一樣應用場景提出了具體協議,如 JWS、JWE、JWK 等,他們能夠統稱爲 JWT,即 Javascript Web Token。flask
JWA 的全稱是 JSON Web Algorithms瀏覽器
JSON 是 Javascript 的語言的文本對象表示法,是一種獨立語言環境的數據結構表示,能夠用網絡數據傳輸,在前面 RESTful 章節中,對 API 調用的返回數據格式就是 JSON。緩存
Algorithms 本義是算法的意思,這裏特指加密算法,也就是用 JSON 表示的數據,通過加密後在在服務器端和客戶段之間傳輸。安全
有了數據結構和加密算法的基礎,根據不一樣的應用場景,定義出了具體實現:
JWT(JSON Web Token)上面 JWS、JWE 和 JWK 的總稱。
JWT Wiki 上的定義是:
JSON Web Token is an Internet standard for creating JSON-based access tokens that assert some number of claims.
大體意思是,JWT 是用基於 JSON 數據結構的生成包含了一些權限聲明的網絡訪問憑證的網絡標準
JWT 由 Header
、Payload
和Signature
,三部分組成,像這樣的形式:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJBdXRobGliIiwic3ViIjoiMTIzIiwibmFtZSI6ImJvYiJ9.cBo6e7Uss5__16mlqZECjHJSKJDdyisevDP5cUGvJms
換行符只是爲了展現用,實際 token 中不包括換行符
用於指定採用的加密算法,以及 JWT 採用的形式類型,例如:
{ "alg" : "HS256", "typ" : "JWT"}
alg
指定前面所用的算法,默認爲 HmacSHA256 簡寫爲 HS256,還有 HS38四、RS256 等typ
是指令牌的類型,JWT 令牌的類型爲 JWT
用於攜帶一些信息,例如用戶名,過時時間 等等,例如:
{ "sub": "1234567890", "name": "John Doe", "admin": true}
JWT 標準定義了 7 個字段:
字段 | 說明 |
---|---|
iss | (issuer):簽發人 |
exp | (expiration time):過時時間 |
sub | (subject):主題 |
aud | (audience):受衆 |
nbf | (Not Before):生效時間 |
iat | (Issued At):簽發時間 |
jti | (JWT ID):編號 |
這些字段有實現這自由選取,也能夠加入其餘自定義字段
首先,須要指定一個密鑰(secret)。密鑰很重要,須要嚴格保密
而後,使用 Header 裏面指定的簽名算法(默認是 HMAC SHA256),按照下面的公式產生簽名:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
即先將 header
和 payload
分別作 base64url 編碼, 而後用 .
將他們鏈接成一個字符串,用加密算法,使用密鑰 secret
, 獲得的加密結果就算簽名
Base64URL 編碼字符集是 Base64 字符集的子集
=
被省略、+
替換成-
,/
替換成_
由於 token 可能經過 URL 進行傳輸,而=
、+
、/
在 URL 中有特殊含義
當客戶端發送請求時將 token 送到服務器端,能夠用和簽名一樣的方式,從新計算一次簽名,若是和客戶端送過來的簽名一致,說明 token 沒有被篡改,若是不一致,說明 token 已被篡改,不安全了。
因而可知,用於作簽名的密鑰 secret 很重要,一旦泄漏,將沒法鑑別 token 的真僞
關於 Python 的 JWT 實現不止一個,不一樣的庫,不一樣的實現方式層出不窮,今天要講解的是 Python 的 Authlib 庫,它是一個大而全的 Python Web 驗證庫支持多種 Python 框架
Authlib 是構建 OAuth 和 OpenID 安全鏈接服務器的終極 Python 庫,包括了 JWS, JWE, JWK, JWA, JWT
Authlib 功能強大而豐富,今天咱們只瞭解他的 JWT 部分,以後在介紹基於第三方認證的 OAuth 技術時還會進一步講解
使用 pip 安裝
pip install Authlib
若是一切正常,能夠導入 Authlib 模板,例如,引入 jwt :
>>> from authlib.jose import jwt>>>
JWT 是服務器端的機制,因此能夠在命令行中作測試
>>> 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'
header
,而且設置簽名算法爲 HS256
payload
,做爲傳輸信息secret
,注意這裏只是方便演示,實際項目中最好是隨機生成,並妥善保存encode
方法,生成 token,encode
方法一次性實現了全部關於 JWT 協議的定義.
分隔爲三部分,前兩部分是 header
和 payload
的 Base64Url 編碼,最後一部分是 簽名接上面的環境:
>>> claims = jwt.decode(token, secret)>>> print(claims){'iss': 'Authlib', 'sub': '123', 'name': 'bob'}>>> print(claims.header){'alg': 'HS256', 'typ': 'JWT'}>>> claims.validate()>>>
decode
方法,利用 secret
對 token
進行解碼,若是簽名正確,就會獲得解碼內容,解碼對象是 authlib.jose.JWTClaims
類的實例payload
內容一致header
,能夠看到 typ
爲JWT
,即便用默認值validate
方法用於檢驗 token 的有效性,好比:是否過時、主題是否一致,是否沒到生效時間等等,也能夠針對每種狀況單獨作驗證,例如validate_exp
可用檢驗是否過時雖然 JWT 理論很繁瑣,但 Authlib 庫提供了簡潔的方法,讓開發應用變得更高效
JWT 之全部流行,有個重要緣由是能夠支持多種客戶端,例如 瀏覽器和 app,JWT 標準規定,通常狀況下,客戶端須要將 token 放在 Http 請求的 Header 中的 Authorization 字段中,舉個例子:
GET /resource HTTP/1.1 Host: server.example.com Authorization: Bearer mF_9.B5f-4.1JqM
用 GET 方式請求 /resource
,在 Header 中添加了 Authorization
字段
不能直接將 token 做爲 Authorization
的值,必須有類型聲明,這裏是Bearer
Bearer
表示這個 token 是由認證服務器生成的,用來作身份識別的,除此以外,IEIT 還定義了其餘 認證類型,如Bisic
,Digest
,能夠簡單理解成Bearer
就是 JWT 的認證類型
除了經過 Http Header 類攜帶 token 以外,還能夠經過 POST 請求主體,以及 URL 中的 querystring 來向服務器發送 token,這兩種狀況下,須要使用 access_token 字段來表示 token
JWT 標準建議使用 Header 方式,除非 Header 沒法使用時才考慮其餘方式
Authlib 主要的用途在打造一個 OAuth 應用,對於單獨作 JWT 的實踐有些麻煩,所以咱們用 flask-jwt 框架,作 JWT 的實踐。
flask-jwt 和以前講述的 flask-login 用法很像,是基於 JWT 的認證的框架,提供和不少方便實踐的特性
pip install Flask-JWT
爲了簡單,將全部代碼放在 app.py 中:
from flask import Flaskfrom flask_jwt import JWT, jwt_required, current_identityfrom werkzeug.security import safe_str_cmp
# User 類,用於模擬用戶實體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 實體集合,用於模擬用戶對象的緩存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}
# 獲取認證的回調函數,從 request 中獲得登陸憑證,返回憑證所表明的 用戶實體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
# 經過 token 得到認證主體的回調函數def identity(payload): user_id = payload['identity'] return userid_table.get(user_id, None)
app = Flask(__name__)app.debug = Trueapp.config['SECRET_KEY'] = 'super-secret'
jwt = JWT(app, authenticate, identity) # 用 JWT 初始化應用
@app.route('/protected', methods= ["GET", "POST"]) # 定義一個 endpoint@jwt_required() # 聲明須要 token 才能訪問def protected(): return '%s' % current_identity # 驗證經過返回 認證主體
if __name__ == '__main__': app.run()
運行:
$ 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
flask-jwt 默認的獲取 token 的路由是/auth
,請求方式是 POST,用 JSON 傳送用戶名密碼給服務器,例如:
$ 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 的結構
flask-jwt 默認經過 Header 傳送 token,爲了和 OAuth 生成的 JWT 作區分,默認使用JWT
做爲 token 的類型,例如,用上面生成的 JWT 請求 /protected
:
curl -H "Authorization: jwt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE...<省略>...VudGl0eSI6MX0.M-shnDPAVdu...<省略>...LaH1EMIbrWjPto" localhost:5000/protectedUser(id='1')
若是 token 有效,則返回 token 對應的認證明體,這個例子中打印出了 user 實體
本節課程講解了基於 token 驗證的 JWT,使用 Authlib 庫對 JWT 作了實踐練習,指望能幫助您更好的理解 JWT,最後經過 flask-jwt 模塊,實踐了 JWT 的驗證方式,和使用方式。在後續的課程中還會對目前流行的第三方認證框架 OAuth 作介紹,敬請期待。
系列文章 第 81 天:NumPy Ndarray 對象及數據類型示例代碼:Python-100-days