[Python] 使用 PyJWT 自己寫製作與解碼 JWT token 的程式
專案裡面常常會碰到 JWT 這個東西,
偶爾就會出現 JWT token 驗證失敗的問題,
這時就得拿 JWT token 與對應的 public key 來驗證。
找了一下網路上,是有不少解碼 (decode) JWT token 的程式,
不過不知是我試驗的方法有問題還是怎樣,
有些程式自己製作出來的 JWT token,用原本的 public key 居然解不開?!
而有些程式只提供了解碼的功能,沒有製作 JWT token 的功能…
決定用 PyJWT 自己寫個簡單的製作 JWT token 與解碼的程式~
下面這個是 jwt_encode.py,它會從 stdin 讀入一個 json 內容,
再用 CLI 參數指定的 private key 檔案簽章,產生一個 JWT token 印在 stdout:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import json import sys import jwt # Read data from stdin data = json.loads(input()) # Read private key from CLI argument private_key = open(sys.argv[1], "rb").read() # Encode jwt encoded = jwt.encode(data, private_key, algorithm="RS256") print(encoded)
下面這個是 jwt_encode.py,它會從 stdin 讀入一個 JWT token 內容,
如果有給 CLI 參數的話,就會解碼並驗證 JWT token 符合 public key;
否則就只是將 JWT token 的內容解碼印出來:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import json import sys import jwt # Read jwt from stdin encoded = input() # Read public key from CLI argument try: public_key = open(sys.argv[1], "rb").read() except Exception as e: public_key = None # Get algorithm from jwt header alg = jwt.get_unverified_header(encoded)["alg"] # Decode jwt if public_key: print(f"* Decoding and verifying with public key, algorithm={alg}...") decoded = jwt.decode(encoded, public_key, algorithms=[alg]) else: print(f"* Decoding without public key, algorithm={alg}...") decoded = jwt.decode(encoded, options={"verify_signature": False}, algorithms=[alg]) print(json.dumps(decoded, indent=2))
來實驗看看吧~
首先用 openssl 指令產生一組公私鑰的組合,
分別存在 public_key.pem 和 private_key.pem:
openssl genrsa -out private_key.pem 2048 openssl rsa -in private_key.pem -out public_key.pem -pubout -outform PEM
public_key.pem 內容會像這樣:
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqh...... ...... EwIDAQAB -----END PUBLIC KEY-----
private_key.pem 內容會像這樣:
-----BEGIN PRIVATE KEY----- MIIEvQIBA...... ...... ZRvkn1u4s0OwtGiFGuW6lzY= -----END PRIVATE KEY-----
接著,我們用 jwt_encode.py 來編碼一個 json 內容:
$ echo '{"a": 1, "b": ["c", "d"], "data": {"name": "John", "age": 9}}' | jwt_encode.py private_key.pem eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJiIjpbImMiLCJkIl0sImRhdGEiOnsibmFtZSI6IkpvaG4iLCJhZ2UiOjl9fQ.PgliGwjO6Jl4Ehg4xf61nGt4SeYsgvm-wYCL-UYcLYPljLqdVc2TgdzO4R7jSUzSl3tmkV02TaYc33M1jjfVlaU4VDrlUGpHJ78gvSHTIActrSKaIhkjmR0j767FM7T1nssQsd1cG_p6KqBtw0vNE83NUGJ2CGn96EkxQtRzKv3WGo8kzoUPtw9jbOP-_bb_7FXBJHKhb3kJd4Vy5zDMXdUWsyOAPKjrTDU0WxQV0FPlX4a3G3ysFtI9N8Agj1s0byFzj3kuzqvHqAJh3MndLlHyerwn8HJNVjESJleQETV5dMipU2kPY-uJ28KUslDomHkGmTAE1jxpjy2IBBoEtA
輸出的 eyJh… 就是 JWT token 的內容。
假設我們拿到這個 JWT token,想要看看裡面存了什麼,
就可以用 jwt_decode.py 來解碼 (先不給 public key):
$ echo "${JWT_TOKEN}" | jwt_decode.py * Decoding without public key, algorithm=RS256... { "a": 1, "b": [ "c", "d" ], "data": { "name": "John", "age": 9 } }
如果手上有 public key 的話,放在參數裡,就會用來驗證這個 JWT token,
如果沒有問題的話,一樣會印出解碼後的內容:
$ echo "${JWT_TOKEN}" | jwt_decode.py public_key.pem * Decoding and verifying with public key, algorithm=RS256... { "a": 1, "b": [ "c", "d" ], "data": { "name": "John", "age": 9 } }
我們可以做出另外一組公私鑰的組合,然後用另一組公鑰來解碼看看…
這時因為 JWT token 並不是用這組私鑰簽章的,所以公鑰驗證會失敗:
$ echo "${JWT_TOKEN}" | jwt_decode.py public_key2.pem * Decoding and verifying with public key, algorithm=RS256... Traceback (most recent call last): File "/jwt_decode.py", line 25, in <module> decoded = jwt.decode(encoded, public_key, algorithms=[alg]) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/jwt/api_jwt.py", line 210, in decode decoded = self.decode_complete( ^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/jwt/api_jwt.py", line 151, in decode_complete decoded = api_jws.decode_complete( ^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/jwt/api_jws.py", line 209, in decode_complete self._verify_signature(signing_input, header, signature, key, algorithms) File "/usr/local/lib/python3.11/site-packages/jwt/api_jws.py", line 310, in _verify_signature raise InvalidSignatureError("Signature verification failed") jwt.exceptions.InvalidSignatureError: Signature verification failed
這樣子就可以確認這兩個 JWT token 的製作與解碼程式是正確無誤的囉~
參考資料:Usage Examples — PyJWT 2.8.0 documentation