OAuth2.0やOpenIDConnectの説明の中でよく目にするJWTとはなにかについて。
基本的にはOpenIDFoundationのドキュメント、JWTを元にしています。
お断り
今回はJWEによる暗号化手順を含めたJWEの詳細には触れません。JWEの翻訳ドキュメントがなくて整理するのが面倒だったので。
また、JWS及びJWE(まとめてJOSEと呼ばれている)については批判も多いですが、批判されている点についても今回は触れません。
概要
JWTはJSONオブジェクト自体。JWSはJWTを署名保護する手段。JWEはJWTを暗号化する手段。
つまりJWTの中には、JWSによって署名されたJWT、JWEによって暗号化されたJWT、その両方が施されたJWTが存在します。
また、プレーンなJWTについても仕様として示されています。
JWT(JSON Web Token)とは
JWTはエンコードされたJSONオブジェクトとしてクレームのセットを表します。
JWTはBase64でエンコードされたパートを.
で連結したURL-safeな文字列として表現されます。
パートの数はJWS、JWEのどちらとして利用されているかによって異なります。
上記から、JWTという単語には二つの解釈が存在しうるようです。
- 後述するJWTクレーム・セットだけを指してJWTと呼ぶ
-
.
で連結したURL-safeな文字列を指してJWTと呼ぶ
様々な筆者がどちらの解釈でJWT
を使っているかは文脈で判断する必要があります。
以後に登場するJWTは全て後者の解釈で利用しています。
JWS(JSON Web Signature)とは
JSONオブジェクトをベースとした構造を用いて、デジタル署名やMACsにより保護されたコンテンツを表現するための手段。
JWT自体はBase64エンコードされただけのJSONオブジェクトなので、やりとりの間に改ざんが行われる可能性があります。
対策として、JWTにJWSによる署名を付与したり、JWEによる暗号化を施します。
クレームのセットとは
JSONオブジェクトは以下のように、0以上の名前/値のペアで表されます。RFC4627
{
"name": "value"
}
この名前:値
のペアがクレーム名:クレーム値
と表現され、ひとまとめにクレームと呼ばれます。
クレームのセットと表現されているのは、複数のクレームの組み合わせのことです。
パートとは
先述の通り、JWSとしてか、JWEとしてか、利用のされ方でパートは異なります。
例として、JWSとして利用されるJWTの場合、以下の3つのパートが存在します(JWEの場合は5つ)。
なお各パートは、パート毎にURF-8のオクテット配列にした上でBase64エンコードが施されています。
- JWTヘッダ(JWSヘッダ)
- JWTクレーム・セット(JWSペイロード)
- JWS署名
JWTヘッダ
JWTヘッダは以下のようなJSONオブジェクトで、JWTに適用される暗号化オペレーションに関するパラメータを持ちます。
{
"typ": "JWT",
"alg": "RSA256"
}
JWTヘッダは、JWTがJWSを用いてデジタル署名されているならJWSヘッダ、JWEを用いて暗号化されているならJWEヘッダとも呼びます。
使用しているアルゴリズムを示すalg
パラメータがnone
の場合、JWTはプレーンJWTであり、JWTヘッダはJWTヘッダでしかありません。
なお同じJSONオブジェクトなのでややこしいですが、JWTクレーム・セット以外のJSONオブジェクトの中身はパラメータと呼ばれます。
クレームと呼ばれるのはJWTクレーム・セットのJSONオブジェクトの中でだけのようです。
JWTクレーム・セット
JWTクレーム・セットは以下のようなJSONオブジェクトで、JWTによって伝搬されるメッセージをクレームのセットで表現しています。
{
"iss":"joe",
"exp":1300819380,
"http://example.com/is_root":true
}
クレーム・セットにはiss
やexp
のような予約済クレームの他に、http://example.com/is_root
のような独自のクレームを含めてもよいとされます。
また、予約済クレームは必須ではないため、クレーム・セットに含まれない場合もあります。
JWTクレーム・セットは、JWTがJWSを用いてデジタル署名されているならJWSペイロードとも呼びます。
プレーンJWTの場合は、JWTクレーム・セットでしかありません。
JWS署名
JWS署名はJWSヘッダとJWSペイロードを.
で連結したURL-safeな文字列に対して、JWSヘッダのalg
で示したアルゴリズムで署名したものになります。
プレーンJWTの場合、JWS署名は作りようが無いですが、JWTとしては以下のように3つ目のパートに空文字が連結されます。
// 本来は改行はしない
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
.
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ
.
署名の検証
JWSによって利用されたJWTについては署名を検証し、改ざんされていないことを確認します。
JWSヘッダーのkid
パラメータのヒントを元に、発信者から公開鍵を取得し、alg
パラメータのアルゴリズムと合わせて署名を検証します。
また、暗号鍵にJWK(RFC7517)が利用されている場合、JWSヘッダーのkid
とJWKのkid
パラメータは一致するように利用できます。
当然ですが、検証は自分で実装しません。危ないので。
鍵と必要な情報を用意したら、各言語のJWTライブラリにお任せしてしまいましょう。
まとめ
JWT、完全に理解できました。
これでOAuth2.0とOIDCのトークンの扱いも完璧です。
実際は署名の検証が終わったらトークン毎に異なる、クレーム・セットの検証が発生するのでこれで完璧では無いですけど。