#JWTとは
タイトルにもある通り、
JSON Web Token
の略で
読み方は、「ジョット」です。
なんか音的に、ジェット機を思い浮かべてしまうのは、私だけでしょうか、、、?
なにかものすごく空を飛ぶ印象を受けてしまいます。なので、やりとりが早そうなイメージがまとわりつきますねえ、これは。
ただただ、便利そう◎
JSON
の本名が、JavaScript Object Notation
なので、
実際は、
JavaScript Object Notation Web Token
ということになるんでしょうかね。
ちなみに、JWTの仕様はRFC7519(外部サイト)で定義されており、
属性情報(Claim:クレーム)をJSONデータ構造で表現したトークンの仕様
※文句のクレームではありませんw
JWTの署名JSON Web Signature(JWS)
、暗号化JSON Web Encryption(JWE)
にまつわる仕様も、
それぞれRFC7515(外部サイト)とRFC7516(外部サイト)に公開されています。
####RFCとは
RFC(Request for Comments)は、インターネットで用いられるさまざまな技術の標準化や運用に関する事項など幅広い情報共有を行うために公開される文書シリーズです。
引用元:日本ネットワークインフォーメーションセンター
#そもそもトークンとは、ナンゾヤ
トークン (英語: token) ,「しるし」「象徴」、「記念品」「証拠品」の意。
引用:wikipedia
ふむふむ、何かを証明するための印みたいなイメージでしょうかね
英語の類義語としては、
symbol, sign, emblem, mark, badge
などになるので、
つまり、感覚的にいうと、「〇〇証」っていうのがしっくりくるような。
例えば、
- 許可証
- 通行証証
- 身分証
などなど。
つまり、JWT
っていうのは、Webアプリケーション上で、「〇〇証」として使う、何か ってことになりますねえ
#JWTの構造と仕組み
実際に、JWTの実態をみてみましょう〜
JWTのデバッグができる**jwt.io**で生成したJWT
そのものを貼り付けてみます。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJpY2hpX3phbXVyYWkiLCJzdWIiOiJ3aGF0X2lzX2p3dD8iLCJuYW1lIjoiaWNoaV96YW11cmFpIiwiZXhwIjoxNjE0NTI0NDAwLCJpYXQiOjE1MTYyMzkwMjJ9.RscJraSmnAivpcaRiM85Mb8BkdXex1A76SfNjis7igg
ただただ、意味不明な文字列が、ダァぁぁああっと横に伸びていますが、、、
しかも横スクロールしないと見えないし、、、、
これだとなんなのか分からないですね。
以下が、作成した時のスクショです。
こちらは**色が付いていて、視覚的に美しい!**
何やら3構造になっている。
そして、.
で色が区切られているではないかぁああ!!!!
つまり、こういうことか、
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJpc3MiOiJpY2hpX3phbXVyYWkiLCJzdWIiOiJ3aGF0X2lzX2p3dD8iLCJuYW1lIjoiaWNoaV96YW11cmFpIiwiZXhwIjoxNjE0NTI0NDAwLCJpYXQiOjE1MTYyMzkwMjJ9
.
RscJraSmnAivpcaRiM85Mb8BkdXex1A76SfNjis7igg
<ヘッダ>.<ペイロード>.<署名>
上述の、横長ベタ貼りのやつは、絶対に.
なんて探すの無理。
まじで、無理。マジで、ウォーリーを探せ状態。
常人には気づけなんて、無理ですね、はい。orz
##JWTは3構造から成っている
###ヘッダー
ヘッダーでは、署名で使うハッシュ値を得るアルゴリズム
とトークンタイプ(データ型)
を指定します。
{
"alg": "HS256",
"typ": "JWT"
}
####<解説>
alg
:署名のハッシュ値を出力するアルゴリズムは、HMAC SHA-256
だよ
typ
:そのオブジェクトがJWT
だよってことを示す
###ペイロード
ペイロードでは、実際に取り扱いたい実際のデータを指定します。
クレームデータとも呼ばれ、自分で任意のプロパティを含ませることもできます。
例えば、
{
"iss": "ichi_zamurai",
"sub": "what_is_jwt?",
"name": "ichi_zamurai",
"admin": true,
"exp": 1614524400,
"iat": 1516239022
}
####<解説>
※name, admin
なんかは任意でいれたプロパティ
※exp
:2021-03-01 00:00:00
まで有効期限を設定してみました
Unixtime相互変換ツールなんかを使うと便利です。
予約語もあって、それぞれの意味はこちらです。
すべて、指定するかどうかは任意(optional)、つまり自由だぁぁぁぁあああ。
予約語 | 意味 | 説明 | 指定データ型 |
---|---|---|---|
iss | Issuer | JWT発行者(サーバー側)の識別子 | 文字列 / StringOrURI |
sub | Subject | JWTの主語となる主体の識別子 | 文字列 / StringOrURI |
aud | Audience | JWT を利用する主体(クライアント側)の識別子 | 文字列/StringOrURI 値の配列 ※単一も可能 |
exp | Expiration Time | 有効期限日時。期限以降にこのJWTの処理はNG | 1970-01-01 00:00:00Zからの秒数を 数値(IntDate)で指定 |
nbf | Not Before | 有効期間開始日時。これ以前にこのJWTの処理はNG。 | 1970-01-01 00:00:00Zからの秒数を 数値(IntDate)で指定 |
iat | Issued At | JWTの発行日時。 | 1970-01-01 00:00:00Zからの秒数を 数値(IntDate)で指定 |
jti | JWT ID | JWTのための一意(ユニーク)な識別子。 重複が起きないように割り当てる必要がある。 |
大文字と小文字を区別する文字列 |
typ | Type | コンテンツタイプの宣言 | 大文字と小文字を区別する文字列 |
###シグニチャー
シグニチャーでは、署名を表します。
翻訳したまんまですね、これじゃまるでわからないのでもう少し解説を。
ヘッダーのオブジェクトを`Base64Url`を使ってエンコード生成した文字列
+
ドット
+
ペイロードのオブジェクトを`Base64Url`を使ってエンコード生成した文字列
,
ソルト(シークレットスパイスを少々)
これを**HS256
のアルゴリズムで、ハッシュ化して生成されたダイジェスト(文字列)が署名**になります。
うーん、なんだかいまいち分からん。
上記の言葉をコード的にまとめると、以下になります。
※以下、なんの言語でもないです、ただのイメージ
data = base64urlEncode( header ) + '.' + base64urlEncode( payload )
signature = Hash( data, secret );
結構簡潔w
つまり、
#ヘッダー
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
#ドット
.
#ペイロード
eyJpc3MiOiJpY2hpX3phbXVyYWkiLCJzdWIiOiJ3aGF0X2lzX2p3dD8iLCJuYW1lIjoiaWNoaV96YW11cmFpIiwiZXhwIjoxNjE0NTI0NDAwLCJpYXQiOjE1MTYyMzkwMjJ9
#秘密キー
hogehogehoge
で生成した署名の文字列が、上記のキャプチャにもあるこれ
RscJraSmnAivpcaRiM85Mb8BkdXex1A76SfNjis7igg
####何故、base64urlEncode
を使うのかというと
それは、形式的にいうと、URL-safe
(URL的に優しい)にするためです。
JWT
は、基本、URIのクエリパラメータとして使用されることを想定していて、
Base64UrlEncodeは、'+'→'-'
、'/'→'_'
、'='→''
に変換してくれる
というのも、+
/
=
は予約文字として、確保されてしまっているので、
その文字列がURLに入ってきてしまうと、特別な能力を発揮してしまうため、
無効化する必要があるんですねえ。
つまり、URL-safe
にするために、Base64UrlEncode
を使っているということだね!
#結局、JWTって何に使うの?
単刀直入に、ユーザー認証に使えます。
何かしらのサービスにログインしたとき、その認証情報をもっておく必要があるが、
その際のユーザー名やその有効期限などを JWT
として保持しておくといった感じです。
エンジニア界隈では、
JWTは危ないやら、セッションに使うべきではないやら、
色々と議論が沸き起こっているみたいですが、
確実に言えることは、認証、それです。
認証の方法として、独自でも作れるみたいですが、
OAuth 2.0
やら、OpenID Connect
やらがあって、
そこでもJWTは活躍してるそうです。
複雑になりすぎので、ここでの説明は割愛します。
#JWTの利便性(メリット)
主に、安全性
、実装のシンプルさ
、管理の容易さ
が特徴です
箇条書きで列挙します〜
- 署名が含まれているため、改ざんがあってもチェックできるようになっていて、一応安全
- 安全なトークンの発行が比較的簡単に実装できる
- 認証確認の際に、署名の検証処理だけで済む
- サーバ側で、ユーザーの認証状態の管理が必要ない(ステートレスに)
- Cookie認証だと、DBにSession情報を保持する必要がある。
- 認証にDB問い合わせが必要ない
- ペイロードに任意のデータを詰められてスケーラブル
- Cookieは4KBで保持できるデータ量が少ない
- Cookieヘッダを使わない場合に、
CORS(Cross-Origin Resource Sharing)
の制約も乗り越えられる - JSONをデコードすれば、フロントエンドからも直接データにアクセス可能
- Cookieを使用しないプラットフォーム(モバイルとか)でもJWTが使え、IoTとかの分野でも認証で活躍できるらしい
- URLのパラメータに含める文字列だから、HTTPリクエストでの取り扱いがシンプル
#JWTによる認証の一例
ざっくりとしたJWTを使った認証の流れは、
ヘッダーorペイロードが改ざんされてないかチェック
→ ペイロードの有効期限(exp)が切れていないかチェック
→ 問題がなければ認証OK
こんな感じですねえ!
###ログイン以前
- フロントエンドからユーザー情報(ID、パスワード等)をリクエスト
- バックエンドで受け取ったIDとパスワードの検証
- OKなら、秘密鍵でJWT発行(※認証されたユーザー情報と有効期限を含める)
- 発行したJWTをHTTPレスポンスヘッダーのCookieに詰めてフロントエンドに返す
- フロントエンドでbase64で変換し、LocalStorageに保存
- ログインは完了
###ログイン以降
認証されたユーザーがブラウザで、AmazonなどのECサイトで商品購入など、認証が必要な操作をした場合に、
- フロントエンドでLocalStorageにトークンがあるか確認
- ある場合は、その有効期限を確認し、HTTPヘッダに入れてバックエンドにリクエスト。
- 切れている場合は、バックエンドに問い合わせし、バックエンドでトークンを更新(リフレッシュ)して返す
- フロントエンドでは、初回登録時と同様に返ってきたトークンをLocalStorageに保存
- 切れている場合は、バックエンドに問い合わせし、バックエンドでトークンを更新(リフレッシュ)して返す
- ない場合は、ユーザーの種類によって適切なページへリダイレクト
- ある場合は、その有効期限を確認し、HTTPヘッダに入れてバックエンドにリクエスト。
- バックエンドで秘密鍵を使って、受け取ったJWTが改ざんされていないかチェック
- バックエンドでJWTの中身を確認して有効期限が切れていないかチェック
- バックエンドでOKならそのまま処理を継続、必要に応じてフロントエンドにデータを返す。
- バックエンドでNGなら、認証エラーをフロントエンドにレスポンス
#JWT認証の注意事項
JWTのヘッダーとペイロードはエンコードされただけのデータなので、
誰でもデコードすればその中身が見えてしまうという事実があります。
盲点ですなあ
なので、改ざんされるリスクがあって、
「でも署名を使ってるから大丈夫だろ〜」って 安心しちゃうじゃないですか?!
そこに落とし穴があって、
{
"alg": "none",
"typ": "JWT"
}
"alg": "none"
とされてしまったときに、
ライブラリによっては、署名のチェックを素通りしてしまうと言う現象が起こります。
ここが盲点となって、
いろいろあんな恐ろしいことやこんな残酷なことをクラッカーたちは攻撃してきますので、
覚えておきましょう。
ライブラリによっては、"alg": "none"
の認証は失敗させるというような処理に対応してきたそうです。
(※具体的にどれがとかは、ちょっとまだ未調査です、すみませぬ。。。)
#まとめ
###JWTは、便利ですねえええ!!!!!!!!!!!!
認証周りは深いいですねぇ
以上、
ありがとうございました!!