第 82 天:Python Web 開發之 JWT 簡介

圖片

在以前的課程中,介紹過  Flask-Login 框架,它是基於 Session 和 Cookie 技術來實現用戶受權和驗證的,不過 Session  有不少的侷限性,這一節介紹一種基於 token 的驗證方式 —— JWT (JSON Web Token),除了對 JWT  的概念講解以外,還有在 Flask 中簡單實踐html

session 的侷限性

基於  Session 的驗證過程大致是:服務器端有一個 Session 詞典,當用戶驗證登陸後,在詞典中爲該用戶建立一個 Session  對象,在響應( response )中返回一個 Session id,當用戶下次請求時,攜帶 Session id,服務器從 Session  詞典中能夠恢復出 Session 對象,以完成用戶的驗證,在用 Session id 從恢復出認證明體。python

從 Session 驗證過程能夠看出一些侷限性:web

  • 服務器橫向擴展很困難:由於 Session 只能存活在一個服務實例中,將用戶請求引導到其餘服務器,將丟掉用戶的登陸狀態
  • 攜帶信息量少,恢復會話信息比較耗時:Session 認證後,客戶端獲得 Session ID, 服務器沒法從 Session ID 中獲得更多信息,須要從數據庫、文件系統或緩存中取得用戶信息,比較耗時
  • 沒有統一標準:Session 由各個服務器框架本身實現,沒有統一標準,存在應用擴展困難的問題,特別加密方式,五花八門,有很大的安全隱患

token 簡介

爲了解決 Session 的問題,有了 token 的驗證方式。算法

token 能夠理解成票據,或者憑證,當用戶獲得服務器的認證後,由服務器頒發,在以後的請求時攜帶,免去頻繁登陸。數據庫

token 不一樣於 Session 的地方:json

  • 能夠獨立於具體的服務器框架生成和校驗
  • 能夠攜帶更多的信息,避免對持久層的查詢操做
  • 基於標準的算法能夠由不一樣的節點完成驗證

爲了利用好  token 的驗證機制,IEIT (互聯網工程任務組),制定了基於 JSON 數據結構的網絡認證方式 JWA(JSON Web  Algorithms),還針對不一樣應用場景提出了具體協議,如 JWS、JWE、JWK 等,他們能夠統稱爲 JWT,即 Javascript  Web Token。flask

理解 JWA

JWA 的全稱是 JSON Web Algorithms瀏覽器

JSON 是 Javascript 的語言的文本對象表示法,是一種獨立語言環境的數據結構表示,能夠用網絡數據傳輸,在前面 RESTful 章節中,對 API 調用的返回數據格式就是 JSON。緩存

Algorithms 本義是算法的意思,這裏特指加密算法,也就是用 JSON 表示的數據,通過加密後在在服務器端和客戶段之間傳輸。安全

有了數據結構和加密算法的基礎,根據不一樣的應用場景,定義出了具體實現:

  • JWS(JSON Web Signature)對數據進行簽名的,用於防止數據被篡改,傳輸不敏感數據的狀況
  • JWE(JSON Web Encryption)對數據作了加密的,用於傳輸敏感數據,具備更好的安全性
  • JWK(JSON Web Key)是經過密鑰對數據進行加密的方法,規定了相應的加密算法

JWT(JSON Web Token)上面 JWS、JWE 和 JWK 的總稱。

JWT 簡介

JWT Wiki 上的定義是:

JSON Web Token is an Internet standard for creating JSON-based access tokens that assert some number of claims.

大體意思是,JWT 是用基於 JSON 數據結構的生成包含了一些權限聲明的網絡訪問憑證的網絡標準

數據結構

JWT 由 HeaderPayload 和Signature,三部分組成,像這樣的形式:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJBdXRobGliIiwic3ViIjoiMTIzIiwibmFtZSI6ImJvYiJ9.cBo6e7Uss5__16mlqZECjHJSKJDdyisevDP5cUGvJms

換行符只是爲了展現用,實際 token 中不包括換行符

Header

用於指定採用的加密算法,以及 JWT 採用的形式類型,例如:

{    "alg" : "HS256",    "typ" : "JWT"}
  • alg 指定前面所用的算法,默認爲 HmacSHA256 簡寫爲 HS256,還有 HS38四、RS256 等
  • typ 是指令牌的類型,JWT 令牌的類型爲 JWT

Payload

用於攜帶一些信息,例如用戶名,過時時間 等等,例如:

{  "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):編號

這些字段有實現這自由選取,也能夠加入其餘自定義字段


Signature

首先,須要指定一個密鑰(secret)。密鑰很重要,須要嚴格保密

而後,使用 Header 裏面指定的簽名算法(默認是 HMAC SHA256),按照下面的公式產生簽名:

HMACSHA256(  base64UrlEncode(header) + "." +    base64UrlEncode(payload),  secret)

即先將 header 和 payload 分別作 base64url 編碼, 而後用 . 將他們鏈接成一個字符串,用加密算法,使用密鑰 secret, 獲得的加密結果就算簽名

Base64URL 編碼字符集是 Base64 字符集的子集
= 被省略、+ 替換成 -/ 替換成_
由於 token 可能經過 URL 進行傳輸,而=+/ 在 URL 中有特殊含義

驗證

當客戶端發送請求時將 token 送到服務器端,能夠用和簽名一樣的方式,從新計算一次簽名,若是和客戶端送過來的簽名一致,說明 token 沒有被篡改,若是不一致,說明 token 已被篡改,不安全了。

因而可知,用於作簽名的密鑰 secret 很重要,一旦泄漏,將沒法鑑別 token 的真僞

JWT 應用

關於 Python 的 JWT 實現不止一個,不一樣的庫,不一樣的實現方式層出不窮,今天要講解的是 Python 的 Authlib 庫,它是一個大而全的 Python Web 驗證庫支持多種 Python 框架

Authlib 的 JWT

Authlib 是構建 OAuth 和 OpenID 安全鏈接服務器的終極 Python 庫,包括了 JWS, JWE, JWK, JWA, JWT

Authlib 功能強大而豐富,今天咱們只瞭解他的 JWT 部分,以後在介紹基於第三方認證的 OAuth 技術時還會進一步講解

安裝

使用 pip 安裝

pip install Authlib

若是一切正常,能夠導入 Authlib 模板,例如,引入 jwt :

>>> from authlib.jose import jwt>>>

小試牛刀

JWT 是服務器端的機制,因此能夠在命令行中作測試

生成 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'
  • 導入 jwt 模塊
  • 定義 header,而且設置簽名算法爲 HS256
  • 定義 payload,做爲傳輸信息
  • 定義 secret,注意這裏只是方便演示,實際項目中最好是隨機生成,並妥善保存
  • 使用 jwt 的 encode 方法,生成 token,encode 方法一次性實現了全部關於 JWT 協議的定義
  • 打印出 token,可見,被 . 分隔爲三部分,前兩部分是 header 和 payload的 Base64Url 編碼,最後一部分是 簽名

解碼 token

接上面的環境:

>>> 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 對 token 進行解碼,若是簽名正確,就會獲得解碼內容,解碼對象是 authlib.jose.JWTClaims 類的實例
  • 打印出解碼內容,能夠看到和生成 token 時的 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 還定義了其餘 認證類型,如 BisicDigest,能夠簡單理解成 Bearer 就是 JWT 的認證類型

除了經過 Http Header 類攜帶 token 以外,還能夠經過 POST 請求主體,以及 URL 中的 querystring 來向服務器發送 token,這兩種狀況下,須要使用 access_token 字段來表示 token

JWT 標準建議使用 Header 方式,除非 Header 沒法使用時才考慮其餘方式

Flask JWT

Authlib 主要的用途在打造一個 OAuth 應用,對於單獨作 JWT 的實踐有些麻煩,所以咱們用 flask-jwt 框架,作 JWT 的實踐。

flask-jwt 和以前講述的 flask-login 用法很像,是基於 JWT 的認證的框架,提供和不少方便實踐的特性

安裝 flask-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

獲取 access_token

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 的結構

使用 access_token

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 作介紹,敬請期待。

參考

  • https://tools.ietf.org/html/rfc7515
  • https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
  • https://docs.authlib.org/en/stable/jose/jwt.html
  • https://en.wikipedia.org/wiki/JSON_Web_Token
  • https://swagger.io/docs/specification/authentication/bearer-authentication/

示例代碼:Python-100-days

系列文章 第 81 天:NumPy Ndarray 對象及數據類型
從 0 學習 Python 0 - 80 大合集總結
相關文章
相關標籤/搜索