※ 本記事に記載されているトークンは学習用のダミーデータであり、実在サービスや秘密情報とは一切関係ありません
🤔 この文字列は何を表しているでしょう?
突然ですが、次の文字列を見てください。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo5OTk5OTk5OTk5OTk5OTk5LCJyb2xlIjoiaG9nZWVlZWVlZWUiLCJ0ZXN0IjoidGVzdCIsImV4cCI6MX0.MgUiFOhKyBSLbAvLv_xSUiqhN1bxSmQhi_iNe8pVz3M
黒魔術でしょうか、いいえ、JWT(JSON Web Token)と呼ばれる認証・認可に利用されるトークンです。
❓ 問題
この JWT は 何を表してるでしょうか?
- 誰?
- どんな権限?
- 本当に信頼できる?
この記事では、JWT の仕組みを Python コードとともに解析しながら 解説していきます。
🔍 JWT とは何か(超ざっくり)
JWT(JSON Web Token)は一言で言うと、
改ざん検知付きの自己紹介カード
です。
- 中身はただの JSON
- 暗号化されていない(誰でも読める)
- でも 勝手に書き換えるとバレる
🧩 JWT の構造
JWT は . で区切られた 3 つのパーツで構成されます。
header.payload.signature
🧪 やってみよう①:JWT の中身を読む
header と payload は Base64URL でエンコードされているだけなので、decode すれば普通に読めます。
import base64
import json
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo5OTk5OTk5OTk5OTk5OTk5LCJyb2xlIjoiaG9nZWVlZWVlZWUiLCJ0ZXN0IjoidGVzdCIsImV4cCI6MX0.MgUiFOhKyBSLbAvLv_xSUiqhN1bxSmQhi_iNe8pVz3M"
def b64url_decode(data: str) -> bytes:
padding = "=" * (-len(data) % 4)
return base64.urlsafe_b64decode(data + padding)
header_b64, payload_b64, _ = token.split(".")
header = json.loads(b64url_decode(header_b64))
payload = json.loads(b64url_decode(payload_b64))
print(header)
print(payload)
実行結果
header
{
"alg": "HS256",
"typ": "JWT"
}
payload
(各自で確認してみてください)
{
"user_id": xxxxxxx,
"role": "xxxxxx",
.....
"exp": xxxxxxxxxxx
}
🔐 JWT が改ざんできない理由(軽く暗号の話)
この JWT は header に "alg": "HS256" と記載されている通り、
HS256 という署名アルゴリズムで署名されています。
- HMAC + SHA-256
- 共通鍵方式(発行者と検証者が同じ秘密鍵を持つ)
-
header.payload全体を 鍵付きハッシュ(HMAC) の入力にする
JWT では、
-
headerとpayloadを.で結合した文字列を作り - それを UTF-8 で encode して byte 列に変換し
- 秘密鍵を使って HMAC-SHA256 を適用し
- 得られた byte 列を Base64URL で encode したものを
signatureとしています
🧪 やってみよう②:署名を検証する
import hmac
import hashlib
def b64url_encode(data: bytes) -> str:
return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")
SECRET = b"my-secret-key"
signing_input = f"{header_b64}.{payload_b64}".encode("utf-8")
expected = hmac.new(
SECRET,
signing_input,
hashlib.sha256
).digest()
print(b64url_encode(expected))
一致すれば正当な JWT、不一致なら偽物です。
🧠 JWT を一言で
JWT は
中身は誰でも読めるが、
正しい鍵を知らないと作り直せない自己紹介カード
✍️ おわりに
JWT はライブラリに任せがちですが、
一度自分で decode / verify してみると理解が一気に進みます。
※ 本記事の JWT トークンは 学習用のダミーデータであり、
実在サービスや秘密情報とは一切関係ありません。
解析コード例
import base64
import json
import hmac
import hashlib
from abc import ABC, abstractmethod
# =====================================================
# Base64URL decode / encode
# =====================================================
def b64url_decode(data: str) -> bytes:
padding = "=" * (-len(data) % 4)
return base64.urlsafe_b64decode(data + padding)
def b64url_encode(data: bytes) -> str:
return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")
# =====================================================
# Algorithm abstract class
# =====================================================
class Algorithm(ABC):
@abstractmethod
def verify(self, signing_input: bytes, signature: bytes) -> bool:
pass
# =====================================================
# HS256
# =====================================================
class HS256(Algorithm):
def __init__(self, secret: bytes):
self.secret = secret
def verify(self, signing_input: bytes, signature: bytes) -> bool:
print("\n[HS256] --- signature analysis ---")
print("secret:", self.secret)
expected = hmac.new(
self.secret,
signing_input,
hashlib.sha256
).digest()
print("expected signature (b64url):", b64url_encode(expected))
return hmac.compare_digest(expected, signature)
# =====================================================
# RS256(今回は実装しませんmm)
# =====================================================
class RS256(Algorithm):
def __init__(self, public_key_pem: bytes):
pass
def verify(self, signing_input: bytes, signature: bytes) -> bool:
pass
# =====================================================
# Algorithm Factory
# =====================================================
def algorithm_factory(alg: str) -> Algorithm:
if alg == "HS256":
return HS256(b"my-secret-key")
# if alg == "RS256":
# pass
raise ValueError(f"Unsupported alg: {alg}")
# =====================================================
# main
# =====================================================
if __name__ == "__main__":
token = (
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo5OTk5OTk5OTk5OTk5OTk5LCJyb2xlIjoiaG9nZWVlZWVlZWUiLCJ0ZXN0IjoidGVzdCIsImV4cCI6MX0.MgUiFOhKyBSLbAvLv_xSUiqhN1bxSmQhi_iNe8pVz3M"
)
print("JWT:", token)
header_b64, payload_b64, signature_b64 = token.split(".")
header = json.loads(b64url_decode(header_b64))
payload = json.loads(b64url_decode(payload_b64))
signature = b64url_decode(signature_b64)
print("\n--- decoded header ---")
print(json.dumps(header, indent=2))
print("\n--- decoded payload ---")
print(json.dumps(payload, indent=2))
signing_input = f"{header_b64}.{payload_b64}".encode("utf-8")
print("\n--- signing input ---")
print("signing_input:", signing_input.decode())
algorithm = algorithm_factory(header["alg"])
valid = algorithm.verify(signing_input, signature)
print("\n--- result ---")
print("signature valid:", valid)