【実践】JWTの作成と検証

スポンサーリンク
JWTについて ノウハウ
JWTについて
この記事は約13分で読めます。

こんにちは。よっしーです。

今日は、JWTについて説明します。

JWTを業務で使用することがあり、jwtについて調べましたので、その共有になります。

まず、「JWT」の読み方ですが、「ジェーダブリューティー」ではなく「ジョット」と読みます。

スポンサーリンク

JWTとは

JWT (JSON Web Token) は、JSON形式でエンコードされたデータの形式で、認証や情報交換のために使用されます。

RFC7519で定義されています。

クレームとは

JWTでは、JSON形式のデータをクレームと呼びます。

そのため、JSONでのキー名は「クレーム名」、値は「クレーム値」と呼ばれます。

クレームは、ユーザーの識別情報、許可、トークンの有効期限などの情報を含んでいます。

// クレームのサンプル
{
    クレーム名: クレーム値,
    クレーム名: クレーム値
}

JWTの構成

JWTは、3つのパートから構成されています。

3つのパートは、ヘッダー、ペイロード、署名です。

各パートをBase64URLエンコードしたデータをドット(.)でつなげたデータをJWSといいます。

[Base64URLエンコードしたヘッダー].[Base64URLエンコードしたペイロード].[Base64URLエンコードした署名]

ヘッダーは、トークンの種類や使用されたアルゴリズムなどの情報を含み、JSON形式でエンコードされます。

ペイロードには、クレームが含まれます。

署名には、ヘッダーとペイロードを使用して、トークンを生成するための秘密鍵が使用されます。

JWTの主な利点は、独自のセッション管理を実装する必要がなく、簡単に実装できることです。

ただし、セキュリティ上の注意が必要です。秘密鍵が漏洩した場合、トークンを偽造される可能性があります。また、トークンの有効期限を適切に設定することも重要です。

JWTにおけるヘッダーとは

JWTにおけるヘッダーは、トークンのメタデータを含むJSON形式のデータ要素であり、JWTの処理方法に関する情報が含まれます。

ヘッダーには、2つの要素が含まれます。

“alg” (アルゴリズム):署名アルゴリズムを指定します。署名に使用されるアルゴリズムは、HS256(HMAC with SHA-256)、RS256(RSA with SHA-256)などがあります。
“typ” (タイプ):トークンのタイプを指定します。JWTの場合、”typ”は常に”JWT”となります。
例えば、以下のようなヘッダーがある場合、HMAC with SHA-256を使用してトークンが署名されていることがわかります。

{
    "alg": "HS256",
    "typ": "JWT"
}

ヘッダーは、Base64 URLエンコードされた形式でトークンに含まれています。

トークンを受け取ったアプリケーションは、ヘッダーから署名アルゴリズムを取得し、トークンの検証に使用します。

また、アプリケーションは、トークンの署名アルゴリズムが安全であることを確認する必要があります。

JWTにおけるクレームとは

JWTにおけるClaimsとは、トークンに含まれるJSON形式のデータ要素のことを指します。トークンの標準化されたフォーマットに従い、構造化された情報を表現するために使用されます。

一般的に、JWTのクレームには3つの種類があります。

Registered Claims (登録済みのクレーム)

  • JWTで使用される一般的なクレームであり、下記の予約されたキーと値のペアで構成されます。
  • iss – issuer (発行者)
  • exp – expiration time date (有効期限の日時)
  • sub – subject (主体)
  • aud – audience (対象)
  • nbf – not before date (有効になる日付)
  • iat – issued at date (発行日)
  • jti – JWT ID (JWT トークンの固有 IDでなければならない)

これらはオプションなので、このタイプのクレームを使用せずに JWT を作成しても有効です。

Public Claims (公開されたクレーム)

登録されていないクレームであり、自由に定義できます。ただし、競合を避けるために、名前空間を使用することが推奨されます。

Private Claims (プライベートなクレーム)

一般的な使用法として定義されていない、特定のアプリケーションや団体が使用するために定義されたクレームです。

クレームのサンプル

クレームの例を以下に示します。

{
    "sub": "1234567890",
    "name": "John Doe",
    "iat": 1516239022
}

上記の例では、”sub”キーにはユーザーのIDが、”name”キーにはユーザー名が、”iat”キーにはトークンが発行された時間が含まれています。

これらのクレームは、トークンの検証や有効期限の管理など、様々な目的で使用されます。

署名について

署名により、JWTは改ざんされた場合に無効になるようになっています。

JWTは、WebアプリケーションやAPIにおいて、ユーザーの認証や権限管理に使用されます。

JWTは、トークンを受け取ったアプリケーションが、発行元のアプリケーションからトークンが正当であることを検証できるようになっています。

JWTを生成するまでの過程

ヘッダーの作成

下記のヘッダー情報を用意します。

{
    "alg": "HS256",
    "typ": "JWT"
}

このヘッダー情報をbase64エンコードします。

% cat header.json  | base64 | sed -e 's/=$//' -e 's/+/-/' -e 's/\//_/'
ewogICAgImFsZyI6ICJIUzI1NiIsCiAgICAidHlwIjogIkpXVCIKfQoK

sedをしているのは、URLセーフにするためです。

base64エンコードされた文字列がヘッダー情報となりますので、これをヘッダー情報として使用します。

ペイロードの作成

下記のペイロードを用意します。

{
    "iss": "yossi",
    "sub": "sample",
    "exp": 1678204800,
    "iat": 1678201200,
    "jti": "7AB636C8-0EE9-4474-8631-9EB3357F5E1A"
}

expは、下記のコマンドで生成しました。

% date -j -f "%Y-%m-%d %H:%M:%S" "2023-03-08 01:00:00" +%s
1678204800

iatは、下記のコマンドで作成しました。

% date -j -f "%Y-%m-%d %H:%M:%S" "2023-03-08 00:00:00" +%s
1678201200

jtiは、固有のIDである必要があるとのことだったので、uuidを利用することにして、下記のコマンドで作成しました。

% uuidgen 
7AB636C8-0EE9-4474-8631-9EB3357F5E1A

このペイロードをbase64エンコードします。

% cat payload.josn | base64 | sed -e 's/=$//' -e 's/+/-/' -e 's/\//_/'
ewogICAgImlzcyI6ICJ5b3NzaSIsCiAgICAic3ViIjogInNhbXBsZSIsCiAgICAiZXhwIjogMTY3ODIwNDgwMCwKICAgICJpYXQiOiAxNjc4MjAxMjAwLAogICAgImp0aSI6ICI3QUI2MzZDOC0wRUU5LTQ0NzQtODYzMS05RUIzMzU3RjVFMUEiCn0

base64エンコードされた文字列がペイロードとなりますので、これをペイロードとして使用します。

署名

まず、ヘッダー情報とペイロードをドットで連結したデータを用意します。

% export header="ewogICAgImFsZyI6ICJIUzI1NiIsCiAgICAidHlwIjogIkpXVCIKfQoK"

% export payload="ewogICAgImlzcyI6ICJ5b3NzaSIsCiAgICAic3ViIjogInNhbXBsZSIsCiAgICAiZXhwIjogMTY3ODIwNDgwMCwKICAgICJpYXQiOiAxNjc4MjAxMjAwLAogICAgImp0aSI6ICI3QUI2MzZDOC0wRUU5LTQ0NzQtODYzMS05RUIzMzU3RjVFMUEiCn0"

% export data="$header.$payload"

% echo $data
ewogICAgImFsZyI6ICJIUzI1NiIsCiAgICAidHlwIjogIkpXVCIKfQoK.ewogICAgImlzcyI6ICJ5b3NzaSIsCiAgICAic3ViIjogInNhbXBsZSIsCiAgICAiZXhwIjogMTY3ODIwNDgwMCwKICAgICJpYXQiOiAxNjc4MjAxMjAwLAogICAgImp0aSI6ICI3QUI2MzZDOC0wRUU5LTQ0NzQtODYzMS05RUIzMzU3RjVFMUEiCn0

上記のデータに署名して、base64エンコードします。

このときに設定する秘密鍵は適当なものを設定しているので、運用で使用する際には適切なものにしてください。

% export secret_key="hogehogehoge"

% echo -n $data \
    | openssl dgst -hmac $secret_key -sha256 -binary \
    | base64 | sed -e 's/=$//' -e 's/+/-/' -e 's/\//_/'
JN0BIVEgIuGCXjyf4rofX1oXTIBAnVRRBjswtL_tqA8

これで、JWTで使用できる署名情報が用意できました。

JWSの生成

上記で作成したヘッダー情報とペイロード、署名をドットで連結した文字列がJWSとなり、JWTとして使用できるトークンになります。

# 完成したJWS
ewogICAgImFsZyI6ICJIUzI1NiIsCiAgICAidHlwIjogIkpXVCIKfQoK.ewogICAgImlzcyI6ICJ5b3NzaSIsCiAgICAic3ViIjogInNhbXBsZSIsCiAgICAiZXhwIjogMTY3ODIwNDgwMCwKICAgICJpYXQiOiAxNjc4MjAxMjAwLAogICAgImp0aSI6ICI3QUI2MzZDOC0wRUU5LTQ0NzQtODYzMS05RUIzMzU3RjVFMUEiCn0.JN0BIVEgIuGCXjyf4rofX1oXTIBAnVRRBjswtL_tqA8

JWSの検証

それでは、上記で作成したJWSの検証をしてみましょう。

検証に使用するコマンドツールは下記になります。

jwt-cli がインストールされていなければインストールします。

# インストール
% brew install mike-engel/jwt-cli/jwt-cli

# 動作確認
% jwt --version
jwt 5.0.3

下記のコマンドで検証できます。

% jwt decode -S hogehogehoge ewogICAgImFsZyI6ICJIUzI1NiIsCiAgICAidHlwIjogIkpXVCIKfQoK.ewogICAgImlzcyI6ICJ5b3NzaSIsCiAgICAic3ViIjogInNhbXBsZSIsCiAgICAiZXhwIjogMTY3ODIwNDgwMCwKICAgICJpYXQiOiAxNjc4MjAxMjAwLAogICAgImp0aSI6ICI3QUI2MzZDOC0wRUU5LTQ0NzQtODYzMS05RUIzMzU3RjVFMUEiCn0.JN0BIVEgIuGCXjyf4rofX1oXTIBAnVRRBjswtL_tqA8

Token header
------------
{
  "typ": "JWT",
  "alg": "HS256"
}

Token claims
------------
{
  "exp": 1678204800,
  "iat": 1678201200,
  "iss": "yossi",
  "jti": "7AB636C8-0EE9-4474-8631-9EB3357F5E1A",
  "sub": "sample"
}

もし改ざんされていれば、下記の結果になります。今回は、使用している秘密鍵が違っている場合を想定して実施しました。

% jwt decode -S hugahuga ewogICAgImFsZyI6ICJIUzI1NiIsCiAgICAidHlwIjogIkpXVCIKfQoK.ewogICAgImlzcyI6ICJ5b3NzaSIsCiAgICAic3ViIjogInNhbXBsZSIsCiAgICAiZXhwIjogMTY3ODIwNDgwMCwKICAgICJpYXQiOiAxNjc4MjAxMjAwLAogICAgImp0aSI6ICI3QUI2MzZDOC0wRUU5LTQ0NzQtODYzMS05RUIzMzU3RjVFMUEiCn0.JN0BIVEgIuGCXjyf4rofX1oXTIBAnVRRBjswtL_tqA8
The JWT provided has an invalid signature

Token header
------------
{
  "typ": "JWT",
  "alg": "HS256"
}

Token claims
------------
{
  "exp": 1678204800,
  "iat": 1678201200,
  "iss": "yossi",
  "jti": "7AB636C8-0EE9-4474-8631-9EB3357F5E1A",
  "sub": "sample"
}

「The JWT provided has an invalid signature」と出力され、署名に誤りがあることがわかります。

おまけ

実は、JWTコマンドが使えれば、下記のコマンドでJWSを作成することができます。

% cat payload.josn | jq -c | jwt encode -S hogehogehoge -A HS256 -
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NzgyMDQ4MDAsImlhdCI6MTY3ODIwMTIwMCwiaXNzIjoieW9zc2kiLCJqdGkiOiI3QUI2MzZDOC0wRUU5LTQ0NzQtODYzMS05RUIzMzU3RjVFMUEiLCJzdWIiOiJzYW1wbGUifQ.bac9JT7Bnxvdun6YtQs-uG6raQWbU2vC4xg6HBmlVsg

検証結果

% jwt decode -S hogehogehoge eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NzgyMDQ4MDAsImlhdCI6MTY3ODIwMTIwMCwiaXNzIjoieW9zc2kiLCJqdGkiOiI3QUI2MzZDOC0wRUU5LTQ0NzQtODYzMS05RUIzMzU3RjVFMUEiLCJzdWIiOiJzYW1wbGUifQ.bac9JT7Bnxvdun6YtQs-uG6raQWbU2vC4xg6HBmlVsg

Token header
------------
{
  "typ": "JWT",
  "alg": "HS256"
}

Token claims
------------
{
  "exp": 1678204800,
  "iat": 1678201200,
  "iss": "yossi",
  "jti": "7AB636C8-0EE9-4474-8631-9EB3357F5E1A",
  "sub": "sample"
}

おわりに

今回は、JWTの説明と作成方法、検証方法について説明しました。

この他に、リフレッシュトークンについても説明したかったのですが、時間がなくなったので、また次回にまわしたいと思います。

それでは、またあしたー

コメント

タイトルとURLをコピーしました