JWT署名検証の仕組み
JWT署名検証のステップバイステッププロセス、署名アルゴリズムがトークンの整合性を保護する仕組み、検証失敗時の動作について解説します。
詳細な説明
署名検証は、JWTを信頼できるものにするプロセスです。これがなければ、誰でも任意のクレームを持つトークンを作成できてしまいます。署名は、トークンが信頼された発行者によって作成され、署名後に変更されていないことを数学的に証明します。
検証プロセスのステップバイステップ:
Input: eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyMTIzIn0.signature_bytes
Step 1: ピリオド区切りでトークンを3つの部分に分割
header_b64 = "eyJhbGciOiJSUzI1NiJ9"
payload_b64 = "eyJzdWIiOiJ1c2VyMTIzIn0"
signature = "signature_bytes"
Step 2: ヘッダーをデコードしてアルゴリズムを決定
header = { "alg": "RS256" }
Step 3: アルゴリズムが許可リストにあるか確認
Allowed: ["RS256"] → OK
Step 4: 正しい検証鍵を選択
RS256の場合: 発行者の公開鍵を使用(JWKSから)
HS256の場合: 共有秘密鍵を使用
Step 5: 期待される署名を再計算
signing_input = header_b64 + "." + payload_b64
expected = RSA_VERIFY(SHA256(signing_input), publicKey)
Step 6: 受信した署名と期待値を比較
一致 → トークンは真正で未改ざん
不一致 → トークンは拒否
整合性の保証:
署名はヘッダーとペイロードの両方をカバーします。攻撃者がいずれかのセクションの1バイトでも変更すると(例えば"role":"user"を"role":"admin"に変更)、署名は無効になります。検証ステップはこの改ざんを検出し、トークンを拒否します。これが、署名入力にペイロードだけでなくヘッダーも含まれる理由です。
kidによる鍵選択:
発行者が複数の署名鍵を維持している場合(鍵ローテーション中に一般的)、JWTヘッダーにはkid(鍵ID)フィールドが含まれます。検証者はkidを使用してJWKSエンドポイントから一致する鍵を選択します。kidに一致する鍵がない場合、検証は失敗します。このメカニズムにより、ダウンタイムなしのシームレスな鍵ローテーションが可能になります。
タイミングセーフな比較:
計算された署名と受信した署名の最終比較には、タイミング攻撃を防ぐために定数時間比較関数を使用する必要があります。比較が最初の異なるバイトでショートサーキットする場合、攻撃者はレスポンス時間を測定して正しい署名を1バイトずつ特定できます。すべての信頼できるJWTライブラリは内部的にタイミングセーフ比較を使用していますが、カスタム実装ではこの点に注意が必要です。
検証が保証しないもの:
署名検証は真正性と整合性を証明しますが、トークンが現在使用可能であることは証明しません。exp、nbf、iss、audクレームは別途確認する必要があります。正しく署名されているが期限切れのトークンは署名検証を通過しますが、クレーム検証ステップで拒否されるべきです。
ユースケース
APIゲートウェイが受信するすべてのJWTに対して署名検証を実行し、発行者のJWKS公開鍵を取得してキャッシュすることで、認証サーバーに連絡せずにトークンを検証します。