JWTの深淵を覗く:JSON Web Tokenの構造とセキュリティのリアル
Web APIの設計において、もはや避けては通れない技術「JWT(JSON Web Token)」。
多くのエンジニアが「認証トークンの一種」としてフワッと理解したまま実装し、そして深夜のトラブルシューティングで泣きを見るケースを数多く見てきました。「なぜトークンが改ざんできないのか」「Base64Urlとは何なのか」。今日は、そんな疑問を抱える現場のエンジニアのために、JWTの裏側を徹底解剖します。
—
1. JWTは「3つのパーツ」が奏でる調和
JWTは、Header、Payload、Signatureという3つのパーツがドット(.)で連結されたシンプルな文字列です。この構造は [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519) に厳格に定義されています。
Header:メタデータの素顔
Headerは、トークンの「自己紹介」です。JSONをBase64Urlエンコードしたもので、主に以下の2つを持ちます。
alg: 署名アルゴリズム(HS256やRS256など)typ: トークンの種類(JWT固定)
Payload:運ばれる真実
Payloadには、ユーザーIDや有効期限(exp)といった「クレーム」を格納します。注意すべきは、ここが暗号化ではなく単なるエンコードである点です。誰でもデコードして中身を読めるため、クレジットカード番号のような機密情報を平文で入れるのは厳禁です。
Signature:信頼の証
これら2つを秘密鍵(または公開鍵)でハッシュ化したものがSignatureです。サーバー側で再計算し、一致しなければ「どこかで誰かがいじったな」と即座に検知できます。
—
2. Base64Urlエンコードの罠
JWTがなぜBase64Urlなのか。それは、URLやHTTPヘッダー(Authorization: Bearer <token>)の中で特別なエスケープ処理をせずにそのまま送れるようにするためです。
標準のBase64との違いは以下の3点。ここを理解していないと、独自でトークン生成ロジックを書いた際に必ずハマります。
1. + を - に置換
2. / を _ に置換
3. 末尾のパディング = を削除
PythonでJWTを自作する際、標準の base64 ライブラリを使うと痛い目を見ます。必ず専用の変換を行うか、PyJWTのような枯れたライブラリを使用してください。
import base64
import json
def base64url_encode(data: dict) -> str:
# JSONを文字列化してバイト列へ
json_bytes = json.dumps(data).encode('utf-8')
# Base64変換し、URLセーフな文字に置換して末尾の=を削除
return base64.urlsafe_b64encode(json_bytes).decode('utf-8').rstrip('=')
# ヘッダーの例
header = {"alg": "HS256", "typ": "JWT"}
print(base64url_encode(header))
—
3. 署名アルゴリズム:HS256 vs RS256
現場で最も議論になるのが「どのアルゴリズムを選ぶべきか」です。
HS256 (HMAC with SHA-256)
- 特性: 共通鍵暗号。サーバーとクライアント(またはマイクロサービス間)で同じ鍵を共有します。
- 用途: シンプルな認証、単一のサービス。
- リスク: 鍵が漏洩すると全トークンが偽造されます。
RS256 (RSA Signature with SHA-256)
- 特性: 非対称鍵暗号。秘密鍵で署名し、公開鍵で検証します。
- 用途: 分散システム、マイクロサービスアーキテクチャ。
- メリット: トークンを検証するサービス側に秘密鍵を渡す必要がないため、セキュリティ強度が段違いです。迷ったらRS256を選んでください。
—
4. 実践:curlで叩くJWTのデバッグ
APIの挙動がおかしいとき、まずはcurlでヘッダーを確認するのが鉄則です。
# APIサーバーからトークンを取得し、ヘッダーを詳細表示する例
curl -v -X POST https://api.example.com/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "password123"}'
受け取ったトークンの中身が正しいか確認する際は、[jwt.io](https://jwt.io/) も便利ですが、機密性の高い環境では自前で検証スクリプトを持つべきです。
import jwt # PyJWTライブラリ
# 公開鍵を使って検証する例
public_key = """-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----"""
try:
# トークンを検証(署名チェックと期限チェックを自動実行)
payload = jwt.decode(token, public_key, algorithms=["RS256"])
print("検証成功:", payload)
except jwt.ExpiredSignatureError:
print("トークンの有効期限が切れています")
except jwt.InvalidTokenError:
print("不正なトークンです")
—
5. 最後に:現場からのアドバイス
JWTは「ステートレス」という強力な武器を持っていますが、その分「一度発行したトークンを即座に無効化する(ブラックリスト管理)」のが難しいという弱点もあります。
大規模なシステムでJWTを導入する際は、必ずexp(有効期限)を短く設定し、必要に応じてRefresh Tokenと組み合わせる設計にしてください。また、署名鍵のローテーション戦略も設計段階で組み込んでおくこと。
ネットワークのパケットを追いかけるように、データの流れと信頼の連鎖を意識する。これが、堅牢なAPIを構築するための唯一の近道です。今日の学びが、あなたの次のアーキテクチャ設計の助けになれば幸いです。
コメント