こんにちは。サイオステクノロジー技術部の武井です。
今回は、JSON Web Tokenの仕様に従ったアクセストークンをLinuxのコマンドラインで作成してみたいと思います。
その前に、JSON Web Tokenを説明したいと思います。
JSON Web Tokenとは?
JSON Web Tokenとは、JSONに電子署名を行い、必要に応じてそのJSONの検証を行い、認証可否を決定する仕様です。OpenIDやOAuthなどに用いられている標準的な仕様です。
セッションを使うことでも同様のことを実現ができますが、JSON Web Tokenの場合は、トークンそのものを検証することで認証可否を判断するので、セッションとは違い、サーバー側で何も持つ必要がありません。これにより、トークンを発行したホストと、トークンを検証するホストが別々でもよいのです。
よくある説明なのですが、イマイチわかりにくいと思います。
JSON Web Tokenの仕様に従って作成されたトークンは以下のようになっています。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e-KAnHN1YuKAnTrigJxudGFrZWnigJ19.97763576b44cbadea825195aa610b3b9ce71a52f228f3a2b41eba30e7e8350ad長い文字列ですが、構成としては、ヘッダー、ペイロード、ヘッダーとペイロードをピリオドでつないで署名したものの3つをピリオドでつなげたものです。つまり以下のような感じです。
[ヘッダー].[ペイロード].[ヘッダーとペイロードをピリオドでつないで署名したもの]ヘッダー、ペイロードをそれぞれ説明します。
ヘッダー
署名アルゴリズムとトークンのタイプを定義します。具体的には以下のようなJSONになります。
{ “alg”: “HS256”, “typ”: “JWT” }これは署名アルゴリズムはHS256、トークのタイプがJSON Web Tokenであることを表しています。トークンのタイプは他にもJWE(JSON Web Encryption)と言うものがあり、これは暗号化まで仕様として定義しているようです。しかしながら、今回は暗号化は行わず、あくまで署名だけですので、SSL等で暗号化することは必須となります。
ペイロード
JSON Web Tokenでやり取りするデータ本体です。例えばユーザー名だったり、認可情報だったり、ユーザーが自由に定義します。例えば以下のような感じです。
{ “sub”: “ntakei” }これは、ユーザー名がntakeiのデータになります。ただ、何でも定義していいというわけではなく、幾つかのフィールド名はJSON Web Tokenの仕様によって予約されています。詳細はRFCを参照頂ければわかるのですが、例えば、expというフィールド名はトークンの有効期限として使われることが決まっているので、使ってはいけません。
次に、トークンを作成するまでの流れ、そしてそのトークンを検証するまでの流れを説明したいと思います。
トークン作成の流れ
トークン作成までの流れは以下のようになります。
(1) ヘッダーをBase64 URL Encode
ヘッダーをBase64 URL Encodeします。Base64 URL EncodeとはBase64でエンコードした上で、更に以下の処理を施します。
- +を-に置換
- /を_に置換
これはURLに、+や/を乗せるとおかしな動作をする可能性があるからです。しかし、個人的には、トークンをURLに付与することがあるのかなと思っています。大抵は、HTTPリクエストヘッダのAuthorizationにセットするのが普通なのではないでしょうか?でもJSON Web Tokenの仕様なので、そのとおりにする必要があります。
(2) ペイロードをBase64 URL Encode
ヘッダーと同様にペイロードもBase64 URL Encodeします。
(3) HS256アルゴリズムとシークレットキーにより署名
(1)と(2)の値をピリオドでつなげて、その値のハッシュ値(不可逆で、その値からはもとの値が類推できないもの)を求めます。ハッシュ値は、HS256アルゴリズムと、サーバー側に保存されたシークレットキーで求めます。このハッシュ値を求めることを署名と言います。
そして、その(1)と(2)と(3)をそれぞれピリオドでつなげたものが、最終的にクライアントに渡されるトークンになります。
このトークンを受け取ったクライアントは、メモリなりデータベースなりローカルストレージなり永続化されるストレージに保存をして、認証で保護されたリソースにアクセスしたときは、ストレージから取得して、サーバーに渡します。
トークンを検証するまでの流れ
クライアントからトークンを受け取ったサーバーが、どうやって認証OKと判断するかを説明します。
(1) HS256アルゴリズムとシークレットキーにより署名
クライアントから受け取ったトークンをピリオドで区切って、左から一つ目と二つ目(つまりヘッダーとペイロード)のハッシュ値を求めます。求め方は、先ほどと同じです。
(2) 一致しているかどうか比較
(1)で求めた値と、トークンをピリオドで区切ったものの中の一番右の部分(つまり、「トークン作成の流れ」でヘッダーとペイロードを署名したもの)が一致しているかどうかを比較します。これが一致していない場合は、認証情報が偽造もしくは改ざんされたものと判断し、認証NGとします。
アクセストークンを自分でつくってみる
Linuxのコマンドを使って、JSON Web Tokenの仕様に基づいたトークンを作って見たいと思います。
下記のようなJSONがあるとします。これらからトークンを作ります。
ヘッダー
{ “alg”: “HS256”, “typ”: “JWT” }
ペイロード
{ “sub”: “ntakei” }
まずヘッダーをBase64 URL Encodeします。
# echo -n '{"alg":"HS256","typ":"JWT"}' | base64 | sed s/\+/-/ | sed -e 's/\//_/' | sed -E s/=+$// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9まずbase64コマンドでbase64エンコードして、sedで+を-に置換、さらにsedで/を_に置換、さらにsedでBase64の最後に付加される=のパディング文字列を削除しています。
同様に、ペイロードもBase64 URL Encodeします。
# echo -n '{“sub”:“ntakei”}' | base64 | sed s/\+/-/ | sed -e 's/\//_/' | sed -E s/=+$// e-KAnHN1YuKAnTrigJxudGFrZWnigJ19次にヘッダーとペイロードをピリオドでつなげたものを、HS256アルゴリズムとシークレットキー(ここではsecret)でハッシュ値を求めます。
# echo -n 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e-KAnHN1YuKAnTrigJxudGFrZWnigJ19' | openssl dgst -sha256 -hmac secret 97763576b44cbadea825195aa610b3b9ce71a52f228f3a2b41eba30e7e8350ad今までの過程で得られた3つの値をピリオドでつなげます。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e-KAnHN1YuKAnTrigJxudGFrZWnigJ19.97763576b44cbadea825195aa610b3b9ce71a52f228f3a2b41eba30e7e8350adこれがJSON Web Tokenの仕様に従ったトークンになります。