JWT(JSON Web Token)は、2者間で安全にクレーム(ユーザー情報など)をやりとりするための方式です。ここでは、JWTの「仕組み」と「セキュリティ上の注意点」について確認します。
JWTの構成要素
3つの構成要素
JWTは以下のような文字列になります。
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.iIr5BW1YfvKF3hK9_1tyf-hGvDs7G7mz8j59pOvi2sp5aX6_Zl0upHXLajbLL574UeB6yQqOxDAh0-WUPnqTLJxbtfIDe3Ni1GWcg4pKf9G0QVOw2EK4_PiSJyf1FAIouXrCgDGJRwFXwIRxlPrTfboCCo68hgXFBAMKLcJW7Pc
ピリオド区切りで3つの役割を持っています。
ヘッダー.ペイロード.電子署名
下記サイトにて、JWTの構造を手軽に確認できます。
ヘッダー|アルゴリズムを伝える
ヘッダー部分は以下のようなJSONを Base64エンコード
した文字列になります。 電子署名
がどういったアルゴリズムで作られているか伝えるのに利用されます。
{
"alg": "RS256",
"typ": "JWT"
}
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9
データを 数字(0~9)
アルファベット(a~z, A~z)
記号(+, /)
の64文字で表すエンコード方式です。
alg
では、以下のようなアルゴリズムを指定できます。
algの値 | 解説 |
---|---|
none | 署名なし |
HS256 | 共通鍵方式。 署名の作成、検証に 共通の鍵 を利用。HMAC using SHA-256 hash |
RS256 | 公開鍵方式。 署名の作成に 秘密鍵 を利用。署名の検証に 公開鍵 を利用。RSA using SHA-256 hash |
SHA-2
というハッシュ関数です。SHA-2の中でも、ハッシュ長が256ビットであることを表しています。SHA-2のハッシュ長は他にもバリエーションがあります( SHA-384
SHA-512
など)。SHA-512
のほうがビット長が大きく安全性が高いですが、一般的に利用されるのはSHA-256
です。
ペイロード|ユーザー情報など
ペイロード部分は以下のようなJSONを Base64エンコード
した文字列になります。
クレーム(認証対象となるエンティティに関する情報)
を含みます( ユーザーID
など)。
{
"sub": "1234567890"
}
eyJzdWIiOiIxMjM0NTY3ODkwIn0
予約項目
クレームには任意の情報を含めることができますが、すでに役割が定義されている 予約項目
も存在します。
予約項目 | 概要 |
---|---|
iss | Issuer 発行者識別子。 |
sub | Subject 同じIssuer内でユニークとなる値。 |
exp | Expiration Time JWTの有効期限。 |
ndf | Not Before JWTが有効となる開始日時。 |
iat | Issued At JWTの発行日時。 |
jti | JWT ID JWTの一意の識別子。重複が生じないように割り当てる。 |
参考
署名(signature)|改ざんの検証
トークンが改ざんされていないか検証するために利用されます。
PHPで署名を作る場合、実装イメージは以下のようになります。
HS256の場合
public function createSignature($header, $payload, $key)
{
$unsignedToken = $this->encodeBase64url($header) . '.' . $this->encodeBase64url($payload);
$signature = hash_hmac('sha256', $unsignedToken, $key, true);
return $signature;
}
RS256の場合
public function createSignature($header, $payload, $key)
{
$unsignedToken = $this->encodeBase64url($header) . '.' . $this->encodeBase64url($payload);
$signature = '';
openssl_sign($unsignedToken, $signature, $key, OPENSSL_ALGO_SHA256);
return $signature;
}
セキュリティ注意点
algの改ざん
ヘッダーはBase64エンコード
されているだけなので、alg
を改ざんすることができます。alg
をnone
HS系
に改ざんして、検証を回避する脆弱性が存在します。
JWT偽造例(alg=none)
1. サーバーからJWTを以下形式で受け取る
algがRS256のヘッダー.ペイロード.電子署名
2. 攻撃者がJWTを改ざん
- algを
none
に改ざん - ペイロードを改ざん
- 署名を削除
algがnoneのヘッダー.改ざんされたペイロード.
3. 改ざんされたJWTをサービスが受けつける
algが none
なので署名の検証が行われずに、改ざんされたペイロードが利用される。
対策
利用可能な alg
をホワイトリスト形式で制限するなどの対応が必要です。
e.g.) RS256
のみ取り扱うように実装する。
SPAで利用したい場合の懸念
SPAでJWTを利用する場合、JWTを保存する場所によって発生するセキュリティ上の脆弱性について理解しておく必要があります。
XSS
や CSRF
の脆弱性に対する根拠のある対策ができていない場合、サーバー側でRedisなどを利用したセッション管理を検討したほうが良いかと思います。
WebStorageの場合
WebStorage
には、Local Storage
(消さない限り永久に保存) と Session Storage
(ウィンドウやタブを閉じるまで有効) があります。以下、WebStorageの特徴です。
- 5MB~10MB保存できる
- JavaScriptでアクセスできるのでXSSに注意が必要
- いくらXSSを気をつけて実装しても、サードパーティのJavaScriptをWebサイトに埋め込むことによって、XSSの可能性を0にできないのでは?
- ただ、IP制限のある管理画面とか、限られた利用ケースであれば問題ないのでは?
- Safariでプライベートブラウジングにアクセスできない
Cookieの場合
- 保存できる最大サイズが小さい(4KB)
- 自動でサーバにデータ送信する
- なのでCSRFに注意が必要
- HttpOnly属性をつけることでJavaScriptからアクセスできなくできる