🎃

JWTってなんだっけ??

2022/08/28に公開

記事を書いた背景

最近、下記の記事と同じような実装をする機会があったのですが、実装していく中で一つ一つの単語の意味などの理解があやふやのまま進めてしまっていたので、ちゃんと理解しようと思い記事を書くことにしました。
https://qiita.com/basho/items/acd6a17bb6e2a2f7a932

今回は 「JWT」 について改めてちゃんと理解できるように書いていきたいと思います。

JWTとは??

Json Web Tokenの略で、属性情報(claim ⇒ ユーザー情報など)をJSON形式で表現したトークンの仕様のことです。 ちなみに読み方は「ジョット」というらしいです。

仕様は「RFC7519」というもので定められており、
JWTによって、認証やアクセス制御の情報などをJSON形式で記述し、一定の手順で符号化した「トークン」を生成することが可能になります。

JWTが一般的に使われるケースとしては、クライアント<=>サーバー間、サービス<=>サービス間の通信時の認証に使用されることが多いかと思います。

JWTの特徴としては、「署名付きのため改ざんを検知できる」、「URLセーフである」点などが挙げられます。
URLセーフというのは文字通り、セーフ(安全)でURLにとって安全な文字列であるということです。逆に?、=、&などの予約文字が入っているような文字列だと、URLが違う意味を持ってしまいます。JWTはこれらの危険な文字を扱っていないことを約束されている、とのことです。

それでは実際のJWTを確認したいと思います。わかりやすいようにピリオドで区切ってみます。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

ご覧の通り、JWTは二つの「.」で区切られており、3つのパートから構成されています。
3つのパートにはそれぞれ意味があり、先頭から順に下記のようになっています。

  1. Header(ヘッダー)
  2. Payload(ペイロード)
  3. signature(署名)
<ヘッダー>.<ペイロード>.<署名>

それでは各パート部分について確認していきたいと思います。

ヘッダー

JWTの署名検証を行う方法を記載するパートで、署名のアルゴリズムやトークンの種類などを記載します。

下記のようなJSONデータをbase64エンコードすることで、ヘッダー情報が生成されます。

{
  "alg": "RS256", => 署名のアルゴリズム
  "kid": "52fa0f122222gadg04e59axxxxxxxxx", => 署名に仕様するキー
  "typ": "JWT" => トークンの種類
}

ペイロード

JWTの本体部分になるところで、やり取りに必要な属性情報(claim)を記載します。このパートはアプリケーション側で任意の値を埋め込むことができます。こちらもJSONをbase64エンコードして、ペイロード情報を生成します。

下記は、よく使われる値の一例です。

{
  "sub": "453665722", => 認証の対象となるユーザの識別子
  "name": "John Doe", => ユーザーの名前
  "iat": 1516239022 => トークンの発行日時を表すタイムスタンプ(issued at)
  "exp": 1660964863, => トークンの有効期限を表すタイムスタンプ(expired at)
  "aud": "xxxxxxxx", => トークンが利用されるべきクライアント(受信者)識別子(audience)
  "iss": "https://xxxxxxxxx", => トークンの発行者を表す識別子 (issuer)
}

もし興味がある方は、下記の公式サイトからJWTを貼り付けることで実際にJWTの中身を簡単に確認できるので、ぜひ試してみてください。面白いと思います。
https://jwt.io/#debugger-io

署名

署名のパートは、トークン作成者の本人証明をするパートです。
下記のように既にエンコード済みヘッダー、ピリオド(".")、エンコード済みペイロードがあります。

<エンコード済みのヘッダー>.<エンコード済みのペイロード>

これらを連結したものを入力値として、本人の「秘密鍵」と「最初に定義した署名アルゴリズム」で計算することで生成されます
今回最初に確認したJWTでは、下記のような計算になりますね。
受信側では公開鍵を使って、検証することで改ざんや偽造が行われていないことを確認します。

Base64URLSafe(Sign('HS256', '${PRIVATE_KEY}',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ'))) ->
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

ようやく署名のところまでの確認が終わったのですが、この署名パートで一つに疑問に思ったことがありました。
簡単にいうと「秘密鍵」と「公開鍵」の関係についてです。
僕はこの二つの関係については、下記の認識を持っていました。
例)

  1. Aが秘密鍵から公開鍵を作成しBに渡す
  2. Bが、その公開鍵を使って通信内容を暗号化する
  3. 暗号化された文書をAが受け取る
  4. Aが秘密鍵を用いて復号し中身を確認する

なので「秘密鍵ではなく、公開鍵で検証するするの??」ということを思ってしまっていました。。
はい、そうです。完全に「暗号化」と「署名」を一緒に考えていました。
恥ずかしいですね。。

「署名」について調べていると、ちゃんと違いについて記載がされていました。。

一方、平文を公開鍵暗号の秘密鍵で処理することを「署名(Sign)」と呼びます。「署名」は受信者だけが持つ秘密鍵でしか作成することができない性質を持ちます。この性質が、紙の文書に施される署名や捺印と、電子文書に署名する人(秘密鍵を持つ人)だけが署名ことができると点で類似しています。
 一方、秘密鍵で作成された署名は、秘密鍵と対になった公開鍵のみで復元することが可能となっています。公開鍵用いて署名から元の情報(電子文書)へ変換することを「検証(Verify)」と呼びます。そこで署名を作成した秘密鍵の持ち主を「署名者」、公開鍵の持ち主を「(署名)検証者」と呼ぶことがあります。
 つまり、公開鍵暗号では「暗号化」と「検証」は公開鍵を用いた変換処理を意味し、「復号」と「署名」は秘密鍵を用いた変換処理を意味しています。しかし、「暗号化」と「検証」、「復号」と「署名」はそれぞれ同一の処理となっています。
 また、特に、電子署名では、秘密鍵を「署名生成鍵」とか「署名生成符号」とよび、公開鍵を「署名検証鍵」とか「署名検証符号」とも呼ぶことがあります。

下記のサイトを参考にさせていただきました

https://www.jipdec.or.jp/project/research/why-e-signature/PKI-crypto-mechanism.html
https://meetup-jp.toast.com/3511
https://zenn.dev/mikakane/articles/tutorial_for_jwt
https://e-words.jp/w/JWT.html

Discussion