みなさんはJWT(Json Web Token)をご存知でしょうか?
OAuthのアクセストークンなどに使われているコレです。
eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIwMmFjOWFlYS0wMzBlLTQ5OTktYThlOC1iY2RkZjQ2MTVmNzYiLCJuYW1lIjoiYmlnbW91bmQiLCJleHAiOjE2OTg4NDE4MDZ9.N_CR35hqAi-Q9cp4kvFHAoYgA_yZ7aMUAOEz235Cen280-GfF3KHsxh7A2jq6TEPD9hD10mhOUcn61BfI3WYoiK0iC4bwVfpV8RtmarbJxtWxfNZbPItYjDsvyOacrUVh-LebA7VHpMP7zHg2wRCQWW9lVaFwVP5A-vGyJDU-D35hQuT0bNVFlp69fp-m2aojHkNxgeEobzhps49ga2hYO6jq86uEAT9kr8bNg4X4rSgHba8rs6eS8zs5twatKPUUKdJl3oYc5gjLR1bo6foIj2FG93Fe65jVutmbY43b79h-rSuZwnwKHhAmKItUqji8XgyM11moaKH69ZFKQ
なんだかワケのわからない文字列が並んでみますが、実はこれは適当に作られた文字列ではなく、文字列自体が情報を持っているのです。
JWTの構成
先ほど記載したJWTを見てみると「.」が2つあります。
これを分割するとJWTは以下の3つに分けることができます
名称 | 分割後の文字列 | 概要 |
---|---|---|
ヘッダー部 | eyJhbGciOiJSUzI1NiJ9 | 署名のアルゴリズムを定義 |
ペイロード部 | eyJzdWIiOiIwMmFjOWFlYS0wMzBlLTQ5OTktYThlOC1iY2RkZjQ2MTVmNzYiLCJuYW1lIjoiYmlnbW91bmQiLCJleHAiOjE2OTg4NDE4MDZ9 | 認証認可などに必要な情報を定義 |
署名部 | N_CR35hqAi-Q9cp4kvFHAoYgA_yZ7aMUAOEz235Cen280-GfF3KHsxh7A2jq6TEPD9hD10mhOUcn61BfI3WYoiK0iC4bwVfpV8RtmarbJxtWxfNZbPItYjDsvyOacrUVh-LebA7VHpMP7zHg2wRCQWW9lVaFwVP5A-vGyJDU-D35hQuT0bNVFlp69fp-m2aojHkNxgeEobzhps49ga2hYO6jq86uEAT9kr8bNg4X4rSgHba8rs6eS8zs5twatKPUUKdJl3oYc5gjLR1bo6foIj2FG93Fe65jVutmbY43b79h-rSuZwnwKHhAmKItUqji8XgyM11moaKH69ZFKQ | ペイロード部が改ざんされていないことを保証 |
※名称は私が仮でつけています。 |
それぞれもう少し深ぼって説明しようと思いますが、理解のしやすさから以下の順番で進めようと思います。
①ペイロード部
②署名部、ヘッダー部
ペイロード部
JWTの大部分の情報が載っています。
突然ですが、以下のPythonのコードは何をやっているかわかりますか?
import base64
import json
s ='eyJzdWIiOiIwMmFjOWFlYS0wMzBlLTQ5OTktYThlOC1iY2RkZjQ2MTVmNzYiLCJuYW1lIjoiYmlnbW91bmQiLCJleHAiOjE2OTg4NDE4MDZ9'
print(json.dumps(json.loads(base64.b64decode(s).decode()), indent=2))
ペイロードの文字列をbase64デコードし、JSONとして読み込んでいます。
その出力結果が以下になります。
{
"sub": "02ac9aea-030e-4999-a8e8-bcddf4615f76",
"name": "bigmound",
"exp": 1698841806
}
JSONが取得できますね。
JWTのペイロード部とはJson Web Tokenという名前の通り、単にJSONの文字列をbase64エンコードしただけなんです。
このJSONのフィールドはClaimと呼ばれ、「それぞれの項目にこんな値をセットしてね!」的なものが定義されています。
例えばexpクレームなどはトークン自体の有効期限が指定されており、JWTの期限の判断にはこのクレームを使っていたりします。
あとはsubクレームでユーザを指定したり、issでトークンの発行元を指定したり、scopesでアクセストークンの認可を指定したりと色々な項目があるので、詳しく知りたい方は下記を見て見てください。
⚠️補足
クレームはJSONなので割と何でも情報を詰め込めてしまいます。
ただ、適当な文字列と思いきやあっさり復号できるので個人情報を入れてはダメです!
署名部、ヘッダー部
ペイロードの説明で、「ペイロードがbase64で有効期限が中身に入っているなら、有効期限改ざんし放題じゃね?」と思った人もいると思います。
それをできないように署名部、ヘッダー部があります。
ざっくり署名部とはペイロード部分に鍵の発行者または利用者しか知り得ない鍵を使用して署名された文字列が格納されています。
そのため、攻撃者がペイロードを改ざんしても、鍵を知り得ないため正しい署名ができず、結果的にJWTの検証で改ざんされたことがバレてしまうワケです。
そして署名をどういったアルゴリズムで計算するかを定義しているのが、ヘッダー部になります。
試しにペイロード部と同じように復号してみると以下のJSONが得られます。
{
"alg": "RS256"
}
今回のJWTではRS256アルゴリズムで署名されていることがわかります。
これらの仕組みにより改ざんされることなくJWTのやりとりができます。
※厳密には署名がついたJWTをJWS(Json Web Signature)と呼ばれます
JWTをどうやって信頼したらいいか
JWTの構成を説明し、どのように利用されるかは理解できたと思いますが、Auth0のようなIDaasで発行されたサービス(以下、認可サーバー)を自分のサービスが信頼するにはどうしたらいいだろうかについて疑問が残ると思います。
方法としては大きく2つあるのでそれぞれ説明します。
署名を自分のサービスで行うパターン
署名の検証を自サービスで行うパターンです。
これには前提として認可サーバーで自サービスが署名検証で使用する鍵を配布してもらう必要があります。
ざっくり以下のような流れが考えられます。
- Auth0が自サービス用に鍵を発行
- 自サービスのユーザがAuth0でJWTを発行
- ユーザがJWTを自サービスに利用する
- 自サービスは1.の鍵を取得して署名を検証する、署名が一致すればAuth0から発行されたものとわかる
- JWTを信頼して認証OKにする
要は信頼しているサービスの鍵を使って署名が通っているからOK!パターンです。
署名を他のサービスで行うパターン
署名の検証を認可サーバーなどに依頼するパターンです。
ざっくり以下のような流れが考えられます。
- 自サービスのユーザがAuth0でJWTを発行
- ユーザがJWTを自サービスに利用する
- 自サービスは1.のJWTを認可サーバなど、JWTを正しく検証できるサービスに投げる
- レスポンスで200であればJWTを信頼して認証OKにする
要は信頼しているサービスの署名が通っているからOK!パターンです。
それぞれメリデメがあるので、気になる人は詳しく調べてみてください。
最後に
文章が長くなりだんだん疲れてきたので一旦ここで終わりにします。
勢いで書いたので誤字脱字などは許してください。
OAuthや他のセキュリティもろもろもちょろっと見識があるのでまた気が向いたら書こうと思います。