Table
一. 前言
在 www 的世界中,Client 與 Server 間的溝通需透過 HTTP 協定發送 request 和接收 response,但因為 HTTP 協議 stateless 無狀態的設計,代表著 Client 與 Server 兩端不會記得先前的狀態,Client 每次發送請求 request 都會被視為是獨立的,也就是說 Server 無法知道 Client 是否已經發送過認證請求。
而為了讓 HTTP 請求保有狀態,上次在 【Flask教學】Flask Session 使用方法和介紹 中介紹了 Cookie 與 Session 的結合(Server Side Session 和 Client Side Session)。將 Session Information 存放在 Cookie 中,讓 Server 在接收到 request 請求時,可以從 Cookie 中取得先前儲存的狀態,例如使用者是否已經登入、或加入過的購物車資訊。
Server Side Session:
[Flask教學] Flask 實作 Session 操作和淺談
如果是在 Cookie 僅存一個 Session_id,而當使用者發送請求時,Server 會根據這個 Session_id 再去資料庫撈取使用者相關資料,來判斷之前儲存的狀態資訊 。
Client Side Session:
而將 Session 資料加密後,儲存於 Cookie,並沒有再由資料庫再去做撈取的方式,這種專業術語叫做 Client Side Session。Flask 內建的 Session 就是採用的就是這種方式,但是也可以使用擴充套件 Flask-Session 來取代儲存於瀏覽器。
上篇文章中【Flask教學系列】實作 Flask Session-base login 登入驗證 介紹了 Session-based Authentication,接下來就是進入今天的正題 token-based Authentication。
二. 什麼是 JWT (JSON Web Token)?
JWT (JSON Web Token) 簡單來說是:使用者在登入或是驗證過身份過後,後端會在返回請求中附上 JWT Token,未來使用者發 Request 時有攜帶此 Token,就表示通過驗證,而沒有攜帶 JWT Token 的使用者就會拒絕請求,需要再重新登入或重新驗證身份。
嚴謹一點的說法是 JWT 將 JSON 結構的資料進行 Base64Url 編碼並加上數位簽章 Signature 後組成 Token 傳遞給 Client 端,然後此 Token 可用於:
- 伺服器端進行驗證身分
- 訊息交換使用
三. JWT 實作流程
▍流程 1 – 產生 JWT Token:
後端收到 Login / Signup 請求時,會產生 JWT Token,並附在 response 內返回; 前端收到 JWT Token 後會將 Token 保存。
▍流程 Part 2 – 驗證 JWT Token:
未來當前端發送請求時,都需要將剛剛 Login / Signup 請求時收到的 JWT Token,放在 HTTP Header、Request Body 或 URL 上的 Query Parameter,三者擇一使用。
後端會利用前端請求所附上的 JWT Token 來驗證是否有權利請求和 JWT Token 資料內是否正確。
關於前端如何保存 JWT Token 最佳實踐方法可以參考此篇: The Ultimate Guide to handling JWTs on frontend clients
四. JWT 組成結構
由上圖可以看到 JWT 其實是由三段亂碼組成,分別是
- Header
- Payload
- Signature
1 2 3 |
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. # header eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9. # payload TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ # signature |
▍Header
Header 存放的是一種聲明,alg 中說明著數位簽章使用的加密演算法是 HS256,而 type 說明這個 token 是 JWT。
1 2 3 4 |
{ "alg": "HS256", "typ": "JWT" } |
然後經過 Base64-Url 編碼過後,就會得到 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 (這段是可以被解密的)
▍Payload
將使用者的狀態存放於此,官方有建議註冊參數,像是 iss (issuer), exp (expiration time), sub (subject), aud (audience), and others.
1 2 3 4 5 6 7 8 |
{ "iss": "Max", "exp": "2021/01/01", "iat": "2020/05/16", "aud": "id0001", "name": "John Doe", "admin": true } |
然後經過 Base64-Url 編碼過後,就會得到 eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9 (這段是可以被解密的)
▍Signature
數位簽章的部分,首先會在 Server 建立一組 secret_key,然後再將 Header、Playload 和這組 secret_key,使用 Hash 256 加密。
未來拿要做驗證時,只需用相同的方法 (收到的 Header + 收到的 Payload + Server 的 key )加密後比對與收到的 JWT 是不是一樣的亂碼,如果有不同就可以知道資料被竄改過了。
1 2 3 4 |
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret_key) |
再加上 Base64-Url 編碼過後,就會得到 TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ (此段是被解密也沒辦法拿到 secret key)
五. Flask 如何實作 JWT
目前 Flask 常見的 JWT 套件大概以下三種:
推薦選擇 Flask-JWT-Extended,因為此套件除了提供實現 JSON Web Tokens 功能外,還提供許多其他功能,像是 Refresh Tokens 功能,或是 Partially protecting routes 功能,這些功能待會都會再詳細介紹。
▍實作簡單範例
Part 0 – 安裝 Flask-JWT-Extended
1 |
pip install flask-jwt-extended |
Part 1 – 載入 & 實例化 JWT
1 2 3 4 5 6 7 |
from flask_jwt_extended import JWTManager jwt = JWTManager() # 設定 JWT 密鑰 app.config['JWT_SECRET_KEY'] = 'this-should-be-change' jwt.init_app(app) |
Part 2 – 建立 JWT Token
當使用者在登入或註冊成功時,使用 create_access_token 來建立 JWT,並將 JWT 返回給前端。
1 2 3 4 5 6 7 8 9 10 |
@app.route('/login', methods=['POST']) def login(): username = request.json.get('username', None) password = request.json.get('password', None) if username != 'test' or password != 'test': return jsonify({"msg": "Bad username or password"}), 401 access_token = create_access_token(identity=username) return jsonify(access_token=access_token) |
使用者登入後,前端會將 access_token 存在 cookie、localstorage、memory 或其他地方,未來呼叫後端 API 時,需要帶上 JWT,可以放在以下三種位置:
1.放在 HTTP Header 裡面
1 |
Authorization: Bearer JWT_token |
2.POST方法:放在 Request Body 裡面
1 |
access_token=JWT_token |
3.GET方法:放在 URI 裡面的一個 Query Parameter
這邊有一個雷,預設值只允許能存取 heades 裡的 jwt token,如果要將 jwt token 放在網址參數 Query Parameter 來使用的話,需要在 config 額外設定如下:
1 |
app.config['JWT_TOKEN_LOCATION'] = ['headers','query_string'] |
預設值參數 query paramater 參數是 jwt,也可以在額外 config 設定 app.config['JWT_QUERY_STRING_NAME'] = jwt
:
1 |
?jwt=JWT_token |
Part 3 – 後端驗證 JWT Token
只需要在 route 下方加上裝飾詞 @jwt_required,就會自動判斷是否有帶入正確的 JWT 了。
1 2 3 4 |
@app.route('/protected', methods=['GET', 'POST']) @jwt_required def protected(): return jsonify(msg='ok') |
基本上到這邊就完成 JWT 的驗證囉!
▍ JWT Refresh Token 的使用
1. 為什麼需要 Refresh Token
一但 JWT Token 被簽發出來,在期限到期之前始終有效,即使使用者的帳號已經被註銷,只要使用者持有 Token 在期效內都會通過驗證;是可以建立黑名單來過濾被註銷的 Token (可參考此篇:redis + blacklist · flask-jwt-extended · GitHub),但就會需要再額外建立 redis 資料庫,就喪失了 JWT 無狀態 Stateless 的特性。
所以為了讓 JWT Token 的過期時間越短越好,又要能避免使用者需要重複登入驗證身份,所以要介紹 Refresh Tokens — flask-jwt-extended 的功用。
當使用者 JWT Token 過期時,Client 設定在背景呼叫 refresh token API, 當收到 refresh token 請求時,對資料庫進行驗證,如果驗證失敗則不產生新的 JWT Token。
這樣 Short-lived JWT + Long-lived refresh token 的方式,可以保有 JWT 無狀態 Stateless 的特性,也因為 Short-lived JWT 可以減緩 JWT Token 無法被註銷的問題。
2. 如何實作 Refresh Token
Refresh Token API 範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
from flask import Flask, jsonify, request from flask_jwt_extended import ( JWTManager, jwt_required, create_access_token, jwt_refresh_token_required, create_refresh_token, get_jwt_identity ) app = Flask(__name__) app.config['JWT_SECRET_KEY'] = 'super-secret' # Change this! jwt = JWTManager(app) @app.route('/refresh', methods=['POST']) @jwt_refresh_token_required def refresh(): current_user = get_jwt_identity() ret = { 'access_token': create_access_token(identity=current_user) } return jsonify(ret), 200 |
JWT Token 過期時間預設為 15 分鐘,Refresh Token 過期時間預設為 30 天,可以依照自己喜好修改:
1 2 3 4 |
import datetime JWT_ACCESS_TOKEN_EXPIRES = datetime.timedelta(minutes=15) JWT_REFRESH_TOKEN_EXPIRES = datetime.timedelta(days=30) |
完整官方文件:
https://flask-jwt-extended.readthedocs.io/en/stable/refresh_tokens/#refresh-tokens
延伸閱讀:
如何製作一次性的 JWT Token,可以參考此篇:https://www.jbspeakr.cc/howto-single-use-jwt/
最後~
▍回顧本篇我們介紹了的內容:
- 前言
- 什麼是 JWT (JSON Web Token)?
- JWT 實作流程
- JWT 組成結構
- Flask 如何實作 JWT
- 為什麼推薦 Flask-JWT-Extended ?
- Part 0 – 安裝 Flask-JWT-Extended
- Part 1 – 載入 & 實例化 JWT
- Part 2 – 建立 JWT Token
- Part 3 – 後端驗證 JWT Token
- Refresh Token
- 為什麼推薦 Flask-JWT-Extended ?
關於 Flask 教學的延伸閱讀:
▍關於 Flask 教學系列目錄:
▍其他 Flask 相關教學:
- 【Flask教學系列】Flask 為甚麼需要 WSGI 與 Nginx
- 【Flask教學系列】實作 Flask CORS
- 【Flask教學系列】實作 Flask CSRF Protection
- 【Flask教學系列】實作 Dockerfile + nginx + ssl + Flask 教學 (附GitHub完整程式)
那麼有關於【Flask 教學系列】Flask-JWT-Extended 實作 的介紹就到這邊告一個段落囉!有任何問題可以在以下留言~
有關 Max行銷誌的最新文章,都會發佈在 Max 的 Facebook 粉絲專頁,如果想看最新更新,還請您按讚或是追蹤唷!