JSON Web Token (JWT)というのを最近知りました。ジョットと読むらしいです。
JWTを使うことで、トークンに任意の情報を持たせたり、署名によりトークンの改ざんの検知することができるようです。
トークンにユーザーの情報を保持できるので、シングルサインオンの仕組みなどに使えるようです。
Yahoo ID 連携などで使われていたりします。
https://developer.yahoo.co.jp/yconnect/v2/id_token.html
JWTを構成する要素
JWTのトークンは、ヘッダー・ペイロード・署名という3つの要素を持っています。
それぞれの要素をBase64でエンコードして「.」で繋げた文字列がトークンです。
ヘッダー.ペイロード.署名
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.Lfn2kKuX9EQrmgnh9XGwrey5b6kgw5CJLgdr74fKliw
ヘッダー
ヘッダーは、署名の生成で使用したアルゴリズムの情報を格納します。
JSONをBase64でエンコードした文字列です。
{
"alg" : "HS256",
"typ" : "JWT"
}
-
alg
・・・署名に使用したアルゴリズムを指定します。none、HS256、RS256などを指定できます。 -
typ
・・・トークンの形式を指定します。JWTにすることが推奨されています。
Base64でエンコードするとこのようになります。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
ペイロード
ペイロードは、 ユーザーを識別できる情報やトークンの有効期限など、任意の情報を格納します。
こちらもJSONをBase64でエンコードした文字列です。
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
任意の情報を格納できますが、役割が定義されているものもあります。
-
iss
・・・ トークン発行者の識別子 -
sub
・・・ トークンの主題の識別子 -
aud
・・・受信者の識別子 -
exp
・・・トークンの有効期限 -
nbf
・・・トークンの有効開始日時 -
iat
・・・トークンの発行日時 -
jti
・・・JWTの一意の識別子
Base64でエンコードするとこのようになります。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
注意が必要なのは、JSONをBase64にエンコードしているだけなので、デコードすれば内容を見れてしまいます。
なので、 パスワードや個人情報など、重要な情報は格納するのはやめましょう。
署名
署名は、Base64にエンコードしたヘッダーとペイロードを「.」で結合して、ヘッダーで指定したアルゴリズムで暗号化して生成します。
署名の生成をPHPで、簡単に実装するとこんな感じになりました。
// ヘッダーとペイロードを結合
$value = $header . "." . $payload . ".";
// 暗号化してBase64にエンコードする
$signature = base64url_encode(hash_hmac('sha256',$value,$key,true));
// 生成結果:Lfn2kKuX9EQrmgnh9XGwrey5b6kgw5CJLgdr74fKliw
トークンの改ざんを検知
ヘッダーとペイロードはBase64でエンコードしたJSONなので、デコードすれば中身を見たり、改ざんすることが可能です。
ですが、 改ざんされたとしても、署名がされているのでトークンを受け取った側でそれを検知することができます。
以下はトークンの改ざんを検証する手順です。
- トークンのヘッダーとペイロードを取り出す。
- 取り出したヘッダーとペイロードを「.」で結合して、ヘッダーで指定されたアルゴリズムで暗号化する。
- 暗号化した内容をBase64でエンコードする。
- エンコードした文字列とトークンの署名部分を比較する。
- 一致しない場合、改ざんされていることがわかるので、トークンを受け付けないなどする。
脆弱性について:ヘッダーのalg
ヘッダーのalg
に none
や HMAC-SHA*
を指定して署名検証を回避する脆弱性があるみたいです。
なので、ホワイトリスト形式でチェックしないといけません。
最後に
実際に使う場面は少ないかもしれませんが、シングルサインオンなどの仕組みが知れた気がするし、
どういった脆弱性があって気を付けないといけないとかを知れたので、調べてよかったと思いました。
次回は、実際にJWTを使ったサンプルを書いてみようと思います。(たぶん)