Python - JWT (JSON Web Token)

Posted on  Dec 1, 2018  in  Python 模組/套件推薦  by  Amo Chen  ‐ 3 min read

JWT(JSON Web Token) 是 RFC 7519 定義的一套標準,用以確保應用(application)之間傳遞訊息的安全性與完整性(integrity)。 JWT 常常與傳統的 Cookie/Session 技術一起被比較,然而這些技術是為了解決不同問題所發明的,也有各自的優缺點與特別合適的應用場景,沒有誰優誰劣的絕對定論。

目前實務上也越來越多應用會利用 JWT 傳遞資料,譬如 APP 在使用者登入時透過 JWT 取得常用的「非機敏性資料」(例如,暱稱、語系設定等等),並且儲存在裝置內,以減少詢問伺服器的次數,達到節省伺服器資源與增加下一次 APP 啟動速度的效果,運用得當的話也是一個加分的技術。

至於為什麼特別強調「非機敏性資料」,本文稍後將作解釋,先一起透過 Python 學習 JWT 相關的概念與使用吧!

本文環境

  • Python 3.6.5
  • PyJWT 1.6.4

安裝 PyJWT

$ pip install PyJWT

JWT 技術原理

JWT 是個很長的字串,例如:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIn0.bqxXg9VwcbXKoiWtp-osd0WKPX307RjcN7EuXbdq-CE

這個字串會用 . 進行分隔,分成 3 個部分:

  1. Header
  2. Payload
  3. Signature (簽章)

如果用上面的範例進行對應就是:

Header: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

Payload: eyJoZWxsbyI6IndvcmxkIn0

Signature: bqxXg9VwcbXKoiWtp-osd0WKPX307RjcN7EuXbdq-CE

其中 Header 與 Payload 是經過 Base64URL 編碼過的結果,所以能夠用 Base64URL 進行解碼(decode):

Header: {"typ":"JWT","alg":"HS256"}

Payload: {"hello":"world"}

而 Signature 是 Header 與 Payload 2 個部分加在一起所做的簽章,簽章的函數為(預設使用 HMAC SHA256,了解原理之後,也可以將 HMAC SHA256 換成其他簽章的演算法):

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  密鑰(secret key)
)

p.s. 密鑰只有產生 JWT 的伺服器端才擁有,因此也只有產生該 JWT 的伺服器才能驗證其正確性(千萬別在前端產生 JWT)

也因為有了簽章,只要伺服器端對簽章進行驗證,就可以知道 JWT 是否被竄改,以確保其可靠性。

JWT 就是透過如此方式所組成的字串,也由於 Header & Payload 是可以解開的,所以 不可以將機敏性的資料(例如密碼)放在其中 ,否則很容易就會被解開導致資料外洩。

以上就是 JWT 的技術原理,接著實際用 PyJWT 產生 JWT 看看吧!

用 PyJWT 產生 JSON Web Token

產生 JWT 的方法很簡單:

>>> import jwt
>>> jwt.encode({'hello': 'world'}, 'secret', algorithm='HS256')
b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIn0.bqxXg9VwcbXKoiWtp-osd0WKPX307RjcN7EuXbdq-CE'

其中 secret 就是密鑰(不可外洩,否則人人都能夠做出一樣的 JWT),而 HS256 則是簽章的算法,也就是預設的 HMAC SHA526 。目前 PyJWT 支援 RS256HS256 2 種簽章算法。

JWT 使用方式

一般來說前端拿到 JWT 可以放在 Cookie, LocalStorage 或者 HTTP Header 中,每次與後端伺服器 API 溝通時,就得提供此 Token 給後端伺服器驗證。

JWT 的問題

產生 JWT 之後,還是有後續的問題得考慮,既然 JWT 是當作 Token 來使用,那麼 Token 由誰發出、何時生效、何時過期等,要如何驗證?

JWT 目前提供 7 種關鍵字,可以放在 Payload 內,讓後端伺服器可以根據這些關鍵字判斷 Token 有效與否:

  1. iss (Issuer) Token 的發行者
  2. sub (Subject) 也就是使用該 Token 的使用者
  3. aud (Audience) Token 的接收者,也就是後端伺服器
  4. exp (Expiration Time) Token 的過期時間
  5. nbf (Not Before) Token 的生效時間
  6. iat (Issued At) Token 的發行時間
  7. jti (JWT ID) Token 的 ID

所以 1 個嚴謹的 JWT Token 可能會這樣產生:

>>> import jwt
>>> from datetime import datetime
>>> payload = {
...     'iss': 'example.com',
...     'sub': 'the_user_id',
...     'aud': 'www.example.com',
...     'exp': datetime.utcnow(),   # must use UTC time
...     'nbf': datetime.utcnow(),
...     'iat': datetime.utcnow(),
...     'jti': 'unique_jwt_id',
...     'hello': 'world',
... }
>>> jwt.encode(payload, 'secret', algorithm='HS256')
b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJleGFtcGxlLmNvbSIsInN1YiI6InRoZV91c2VyX2lkIiwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwiZXhwIjoxNTQzNjUwMjg3LCJuYmYiOjE1NDM2NTAyODcsImlhdCI6MTU0MzY1MDI4NywianRpIjoidW5pcXVlX2p3dF9pZCJ9.FXnVl2ZOQTrPV-LRstVEm_Bi2em7b85fobt6BjTe5PA'

如果前端將上述 JWT 傳送至後端時,後端可以使用 jwt.decode 函示試著解開該 JWT:

>>> jwt.decode(b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJleGFtcGxlLmNvbSIsInN1YiI6InRoZV91c2VyX2lkIiwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwiZXhwIjoxNTQzNjUwMjg3LCJuYmYiOjE1NDM2NTAyODcsImlhdCI6MTU0MzY1MDI4NywianRpIjoidW5pcXVlX2p3dF9pZCJ9.FXnVl2ZOQTrPV-LRstVEm_Bi2em7b85fobt6BjTe5PA', 'secret', audience='www.example.com', issuer='example.com')
{'iss': 'example.com', 'sub': 'the_user_id', 'aud': 'www.example.com', 'exp': 1543659351, 'nbf': 1543649351, 'iat': 1543649351, 'jti': 'unique_jwt_id', 'hello': 'world'}

正確的話會得到 Payload 回傳。

如果是假造的 Token 的話則會拋出 Exception:

jwt.exceptions.InvalidSignatureError: Signature verification failed

如果是過期的 Token 則會拋出 Exception:

ExpiredSignatureError: Signature has expired

如果 Token 的發行者 (issuer) 有問題的話,則會拋出 Exception:

InvalidIssuerError: Invalid issuer

這邊還要注意一件事,目前 PyJWT 只支援以下 5 個關鍵字的驗證:

  • “exp” (Expiration Time) Claim
  • “nbf” (Not Before Time) Claim
  • “iss” (Issuer) Claim
  • “aud” (Audience) Claim
  • “iat” (Issued At) Claim

所以 subjti 的驗證,還是得在後端額外利用程式驗證,例如驗證 sub 的資料是否正確、 jti 所對應的 Token 是否被停用等等。

如此一來, JWT 的安全性就相對有保證一些。

以上就是 JWT 相關的概念與使用囉, Happy Coding!

References

https://jwt.io/introduction/

https://tools.ietf.org/html/rfc7519

https://pyjwt.readthedocs.io/en/latest/index.html

對抗久坐職業傷害

研究指出每天增加 2 小時坐著的時間,會增加大腸癌、心臟疾病、肺癌的風險,也造成肩頸、腰背疼痛等常見問題。

然而對抗這些問題,卻只需要工作時定期休息跟伸展身體即可!

你想輕鬆改變現狀嗎?試試看我們的 PomodoRoll 番茄鐘吧! PomodoRoll 番茄鐘會根據你所設定的專注時間,定期建議你 1 項辦公族適用的伸展運動,幫助你打敗久坐所帶來的傷害!

贊助我們的創作

看完這篇文章了嗎? 休息一下,喝杯咖啡吧!

如果你覺得 MyApollo 有讓你獲得實用的資訊,希望能看到更多的技術分享,邀請你贊助我們一杯咖啡,讓我們有更多的動力與精力繼續提供高品質的文章,感謝你的支持!