JWTとは何か?
概要
JSON Web Token(JWTと略され、「ジョット」と呼ばれます)は、URLセーフにJSONオブジェクト(Claim Set)を表現するフォーマットです。
具体的には、Base64URLエンコードしたJSONオブジェクトをJSON Web Signature(JWS) や JSON Web Encryption(JWE)のペイロードとして表現します。
結果的に、JWSやJWEの仕様により、Base64URLエンコードされたものをピリオド(.
)で連結したものの一部としてそのJSONオブジェクトが表現されます。
JSON Web Token (JWT) is a compact claims representation format
intended for space constrained environments such as HTTP
Authorization headers and URI query parameters. JWTs encode claims
to be transmitted as a JSON [RFC7159] object that is used as the
payload of a JSON Web Signature (JWS) [JWS] structure or as the
plaintext of a JSON Web Encryption (JWE) [JWE] structure (omit).
The suggested pronunciation of JWT is the same as the English word "jot".
JWTs represent a set of claims as a JSON object that is encoded in a
JWS and/or JWE structure. This JSON object is the JWT Claims Set.
A JWT is represented as a sequence of URL-safe parts separated by
period ('.') characters. Each part contains a base64url-encoded
value.
JWTで表現されるJSONオブジェクトはClaims Set(Claimの集合)と呼ばれます。ClaimとはJSONのkey, valueの一対を意味します。
JWT Claims Set
A JSON object that contains the claims conveyed by the JWT.
Claim
A piece of information asserted about a subject. A claim is
represented as a name/value pair consisting of a Claim Name and a
Claim Value.
例えば以下のClaim SetをBase64URLエンコードすると以下のようになります。
{"iss":"joe",
"exp":1300819380,
"http://example.com/is_root":true}
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ
そして(後述する)JWSのCompact Serialization形式のペイロードとして表現すると以下のようになります。
eyJhbGciOiJub25lIn0
.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ
.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
これ全体は、JWSのCompact Serialization形式でもありJWTでもあります。
Claims Set
JWTには、三種類のClaimに分類されます。
https://tools.ietf.org/html/rfc7519#section-4
-
Registered Claim Names
IANAの「JSON Web Token Claims」に予め登録されたもので、強制ではないですが推奨されています。 -
Public Claim Names
利用者が自由に名前を決めることができますが、IANAの「JSON Web Token Claims」に登録するか、公的に衝突しない名前である必要があります。 -
Private Claim Names
発行者と利用者間で合意が取れている名前です。
JWSとは何か?
JWTとの違い
先ほど、JWTはJSONオブジェクトをURLセーフに表現するフォーマットであることが分かりました。
しかし、それだけだと誰でもBase64URLデコードをして、JSONオブジェクトの中身を見たり、変更したりすることができてしまいます。
それに対処するために、URLセーフでかつ署名を付与して改竄されていないことを保証するフォーマットがJWSで、暗号化を施すのがJWEになります。
JWTはJWSと混同して表現されることが多いですが、JWTの定義からJWSはペイロードがJSONオブジェクト(Claim Set)の場合にJWTでもあることになります。
しかし、JWSはペイロードは任意でJSONオブジェクトに限定していません。したがって、自分の理解が正しければ、JWS may be JWT という関係になります。
イメージとしては、チョコレート菓子(JWT)とケーキ(JWS)のような関係で本質的には別の物ですが、JSONオブジェクトをペイロードとした時はチョコレートケーキになるような理解です。
JWS Payload
The sequence of octets to be secured -- a.k.a. the message. The
payload can contain an arbitrary sequence of octets.
The UTF-8 representation of the following JSON object is used as the
JWS Payload. (Note that the payload can be any content and need not
be a representation of a JSON object.)
ここでは、JWSについて確認していきます。
構成
JWSは、JSONオブジェクトに署名を付与したフォーマットであり、JWTを利用することができます。
そして主に以下の三つで構成されます。
https://tools.ietf.org/html/rfc7515#section-3
- JOSE Header
- JWS Payload
- JWS Signature
また、JWSには二つのシリアライゼーション形式があります。
- JWS Compact Serialization
- JWS JSON Serialization
JWS Compact Serialization はヘッダーは署名付きのもの(Protected Header)のみで、一つの署名を適用できます。
JWS JSON Serialization はヘッダーに署名付きのものとそうでないもの(Unprotected Header)を持つことができ、複数の署名を適用できます。
JWS Compact Serialization
JWS Compact Serialization の場合は以下のような構成になります。
BASE64URL(UTF8(JWS Protected Header = JOSE Header)) || '.' ||
BASE64URL(JWS Payload) || '.' ||
BASE64URL(JWS Signature)
まず、JOSE Headerを作成し、Base64URLエンコードします。
JOSE Headerとは、ヘッダーとペイロードに対する署名アルゴリズムを表現したJSONオブジェクトです。
For a JWS, the members of the JSON object(s) representing the JOSE
Header describe the digital signature or MAC applied to the JWS
Protected Header and the JWS Payload and optionally additional
properties of the JWS.
typ
でデータ構造がJWTによるものであることを明示し、alg
で署名に使われるアルゴリズムを指定します。alg
は必須です。
{"typ":"JWT",
"alg":"HS256"}
これをBase64URLエンコードします。
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
同様に実際に送りたいデータ(ペイロード)をBase64URLエンコードします。
データ形式は任意でこの例ではJSONオブジェクトを利用しています。
{"iss":"joe",
"exp":1300819380,
"http://example.com/is_root":true}
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ
次に、Base64URLエンコードしたJOSEヘッダーとペイロードをピリオドで連結したものに対して署名をし、これもBase64URLエンコードします。
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
最後に、Header.Payload.Signatureの順にピリオドで連結するとJWS(JWS Compact Serialization 形式)が完成します。
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
逆にこれを受け取って検証するときは、Base64URLデコードし、署名アルゴリズムがヘッダーから分かり、署名の検証を行います。
JWTの使い所とセキュリティに関する問題
ここまで、JWTの概要について確認してきましたが、これらはどのような場合に利用されるべきものでしょうか?
結論から言うと、JWTはURLセーフな、一度使って終わりのようなトークンとしては利用できそうです。逆に、一定期間有効なログインセッションなどのセッションデータとして利用すべきではありません。
セッション情報にJWTを利用した場合の問題
ログインセッションにJWTを利用するという話は聞くことがありますが、以下のような問題点があります。基本的にこの記事がわかりやすくまとめられています。
ログインセッションにJWTを利用するとは、ログインしたユーザーの情報(ユーザーIDなど)をClaim SetとしたJWTをHTTPヘッダに付与してクライアント側のCookieにセットし、クライアントからリクエスト時にJWTもサーバーに送り、サーバー側はそれを元にどのユーザーからのリクエストであるかを特定すると行った仕組みです。
1 . URLやCookieに格納できるサイズを超える可能性がある
RFC6265ではCookieは、少なくとも 4096 bytes のサイズの制限がなければならないとありますが、Claim Setが大きかったり、Base64URLエンコードをすることによってその上限を超える可能性があります。Cookieに格納できない場合はローカルストレージに格納するくらいしかありませんが、JavaScriptから操作が可能になり、HttpOnlyなどの恩恵が受けられなくなります。
2 . 発行したJWTだけを失効できない
セッション情報としてJWTを発行すると、その有効期限が切れるまでそのJWTだけを失効させる方法がありません。
そのため、アカウントが攻撃者に乗っ取られた場合や、変更のパスワードでログインした場合、ログアウトしている場合などでもJWTの有効期限を迎えるまで、そのJWTはそのユーザーのログイン情報として利用できてしまいます。
それに対処するには、JWSの場合であれば署名アルゴリズムに用いる鍵を変えることができますが、全てのそれまで発行したトークンが失効します。
一方で伝統的なKeyValueストアにセッション情報を保持し、それに対応するKeyからセッションIDを生成しクライアントに渡す方法であれば、KeyValueストアの対応するセッション情報を削除すれば有効期限に限らず失効させることができます。
3 . 伝統的なセッション管理の方法に対して使い古されていない
仮に上記のような問題が解決できたとしても、伝統的なセッション管理を実現する実装やライブラリはすでに多く利用されており、それに伴い多くのセキュリティ面での改善が加えられています。
そのような恩恵を受けられない点が差になります。
これらのことから、JWTは有効期限がかなり短いものや、一度利用されて終わりのもの(使い回されない)、機密性の低いサイズの小さい情報をURLセーフに送るものなどでは利用できそうです。
特に以下の記事を参考にさせていただきました。