JWT はステートレスです。一度発行されると、有効期限が切れるまで有効なままです。jwt-logout プラグインは Redis を使用して軽量なセッション追跡を追加し、JWT がデフォルトで欠いている 2 つの機能を実現します。
トークン失効: 特定の JWT を有効期限が切れる前に無効化します。
シングルデバイスログイン: アカウントを一度に 1 つのアクティブセッションに制限し、新しいデバイスでログインすると以前のセッションが自動的に無効化されるようにします。
このプラグインには、ゲートウェイバージョン 1.2.32 以降が必要です。
仕組み
このプラグインは、失効したトークンとアクティブセッションを追跡するために、JWT メタデータを Redis に保存します。
トークン失効フロー
クライアントは、構成されたログアウトサフィックス (デフォルト:
/jwt_logout) で終わるパスにリクエストを送信します。ゲートウェイは、JWT ペイロードから識別フィールド (デフォルト:
jti) を抽出し、Redis にキーを書き込みます。後続のリクエストごとに、ゲートウェイは JWT の識別フィールドが Redis 内のキーと一致するかどうかを確認します。一致が存在する場合、リクエストは
401応答で拒否されます。Redis キーは JWT の
expクレームに基づいて自動的に有効期限切れとなるため、手動でのクリーンアップは不要です。
シングルデバイスログインフロー
クライアントは有効な JWT を含むリクエストを送信します。ゲートウェイは、ペイロードから構成された識別フィールド (デフォルト:
iss、aud、sub) を抽出し、Redis を確認します。一致するキーが存在しない場合、ゲートウェイは完全な JWT を Redis に書き込み、リクエストを許可します。この JWT が「アクティブセッション」になります。
一致するキーが存在するが、保存された JWT が現在のものと異なる場合、ゲートウェイは
403応答でリクエストを拒否します。これは、アカウントがすでに別のデバイスでアクティブであることを意味します。アクティブセッションを新しいデバイスに切り替えるには、構成されたログインパスサフィックス (デフォルト:
/jwt_login) にリクエストを送信します。ゲートウェイは保存された JWT を上書きし、以前のセッションを無効化します。
プラグインタイプ
認証
構成リファレンス
プラグイン構成には、主に 3 つのブロックがあります。
redis: # 必須。Redis 接続設定。
logout: # オプション。トークン失効設定。
login: # オプション。シングルデバイスログイン設定。トップレベルフィールド
| 名前 | タイプ | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
jwks | 文字列 | いいえ | - | JWT 署名検証用の JSON Web Key Set (JWKS)。jwt-auth プラグインと併用する場合は不要です。「RFC 7517」をご参照ください。 |
clock_skew | 数値 | いいえ | 60 | exp および iat クレームの検証時に許容される時計のオフセット。単位: 秒。 |
token_header | 文字列 | いいえ | Authorization | JWT を含むリクエストヘッダー。 |
token_prefix | 文字列 | いいえ | Bearer | JWT を解析する前にヘッダー値から削除するプレフィックス。 |
redis | オブジェクト | はい | - | Redis 接続設定。以下の Redis フィールドをご参照ください。 |
logout | オブジェクト | いいえ | - | トークン失効設定。省略された場合、トークン失効は無効になります。以下のログアウトフィールドをご参照ください。 |
login | オブジェクト | いいえ | - | シングルデバイスログイン設定。省略された場合、シングルデバイスログインは無効になります。以下のログインフィールドをご参照ください。 |
Redis フィールド
| 名前 | タイプ | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
service | 文字列 | はい | - | Redis サービス名。形式はサービスソースに依存します: my-redis.static (固定アドレス)、my-redis.dns (DNS ドメイン名)、または my-redis.default.svc.cluster.local (ACK)。 |
port | 数値 | はい | - | Redis サービスポート。 |
username | 文字列 | いいえ | - | Redis AUTH コマンドのユーザー名。 |
password | 文字列 | いいえ | - | Redis AUTH コマンドのパスワード。 |
timeout | 数値 | いいえ | 1000 | Redis コマンドタイムアウト。単位: ミリ秒。 |
ログアウトフィールド
| 名前 | タイプ | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
key_prefix | 文字列 | いいえ | higress_jwt_logout_ | Redis キーのプレフィックス。 |
key | 文字列の配列 | いいえ | ["jti"] | トークンを識別する JWT ペイロードフィールド。これらのフィールドに同一の値を持つトークンは、同じトークンとして扱われます。JWT ペイロードにこれらのフィールドのいずれかが欠落している場合、ゲートウェイは 401 invalid token を返します。 |
path | 文字列 | いいえ | /jwt_logout | ログアウトをトリガーする URL パスサフィックス。リクエストパスがこの値で終わる場合、リクエスト内の JWT は失効します。 |
error_status | 数値 | いいえ | 401 | 失効したトークンが使用されたときに返される HTTP ステータスコード。 |
error_body | 文字列 | いいえ | {"message":"invalid token"} | 失効したトークンが使用されたときに返される応答本文。 |
ttl | 数値 | いいえ | - | Redis キーの存続期間 (TTL)。単位: 秒。トークンが失効状態を維持する期間を決定します。デフォルト: exp クレーム値から現在時刻を引いた値。JWT に exp クレームがない場合、デフォルトは 86,400 秒 (24 時間) です。 |
ログインフィールド
| 名前 | タイプ | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
key_prefix | 文字列 | いいえ | higress_jwt_logout_ | Redis キーのプレフィックス。 |
key | 文字列の配列 | いいえ | ["iss","aud","sub"] | アカウントセッションを識別する JWT ペイロードフィールド。これらのフィールドに同一の値を持つトークンは、同じアカウントと見なされます。JWT ペイロードにこれらのフィールドのいずれかが欠落している場合、ゲートウェイは 401 invalid token を返します。 |
path | 文字列 | いいえ | /jwt_login | 強制ログインをトリガーする URL パスサフィックス。このパスにリクエストを送信すると、Redis 内のアクティブセッションが現在の JWT で上書きされます。 |
error_status | 数値 | いいえ | 403 | 重複ログインが検出されたときに返される HTTP ステータスコード。 |
error_body | 文字列 | いいえ | {"message":"already login on other device"} | 重複ログインが検出されたときに返される応答本文。 |
ttl | 数値 | いいえ | - | Redis キーの存続期間 (TTL)。単位: 秒。ログインセッションがアクティブ状態を維持する期間を決定します。デフォルト: exp クレーム値から現在時刻を引いた値。JWT に exp クレームがない場合、デフォルトは 86,400 秒 (24 時間) です。 |
Redis キー形式
プラグインは、次のパターンで Redis キーを構築します。
<key_prefix><PayloadKey1>#<PayloadKey2>##<PayloadValue1>#<PayloadValue2>例:
ログアウトキー:
higress_jwt_logout_jti##xxxxログインキー:
higress_jwt_login_iss#aud#sub##abcd#www.example.com#test
ApsaraDB for Redis インスタンスへの接続
このプラグインの Redis 接続を設定するには:
ApsaraDB for Redis インスタンスを作成します。詳細については、「概要」をご参照ください。
インスタンスの VPC エンドポイント (例:
r-xxxxxxx.redis.rds.aliyuncs.com) を取得します。MSE コンソールでサービスを追加します。[サービスソース] ドロップダウンリストから [DNS ドメイン名] を選択し、[サービスポート] フィールドに Redis ポート (ほとんどの場合 6379) を入力し、[ドメイン] フィールドに VPC エンドポイントを入力し、[TLS モード] を [無効] に設定します。詳細については、「サービスを追加」をご参照ください。
Redis 接続ブロックをプラグイン構成に追加します。Redis インスタンスに認証が必要な場合は、パスワードを含めます。
redis: service: redis.dns port: 6379redis: service: redis.dns port: 6379 password: <your-redis-password> # 実際のパスワードに置き換えてください。
キャパシティプランニング: 各リクエストは、ログアウトチェックのために 1 回の Redis 読み取りをトリガーし、ログインチェックのために 2 回の読み取りをトリガーします。ログアウトおよび初回ログインリクエストは、2 回の追加書き込みを生成します。Redis キャパシティを推定するには、ゲートウェイリクエストスループットを 2 倍にしてください。
例: ログアウト時の JWT 失効
特定の JWT を無効化し、認証に使用できないようにします。
プラグイン構成:
redis:
service: redis.dns
port: 6379
jwks: |
{
"keys": [
{
"kty": "oct",
"kid": "123",
"k": "hM0k3AbXBPpKOGg__Ql2Obcq7s60myWDpbHXzgKUQdYo7YCRp0gUqkCnbGSvZ2rGEl4YFkKqIqW7mTHdj-bcqXpNr-NOznEyMpVPOIlqG_NWVC3dydBgcsIZIdD-MR2AQceEaxriPA_VmiUCwfwL2Bhs6_i7eolXoY11EapLQtutz0BV6ZxQQ4dYUmct--7PLNb4BWJyQeWu0QfbIthnvhYllyl2dgeLTEJT58wzFz5HeNMNz8ohY5K0XaKAe5cepryqoXLhA-V-O1OjSG8lCNdKS09OY6O0fkyweKEtuDfien5tHHSsHXoAxYEHPFcSRL4bFPLZ0orTt1_4zpyfew",
"alg": "HS256"
}
]
}
logout:
path: "/jwt_logout"
key: ["jti"]
error_status: 401
error_body: |
{"message":"invalid token"}ステップ 1: ログアウトのトリガー。
curl http://xxx.hello.com/test/jwt_logout \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJ4eHh4IiwiaXNzIjoiYWJjZCIsInN1YiI6InRlc3QiLCJhdWQiOiJ3d3cudGVzdC5jb20iLCJpYXQiOjE2NjU2NjA1MjcsImV4cCI6MTg2NTY3MzgxOX0.tmKF6qc1mOWNyCCzBOT2XKNoEGeEgr3EbhTKAQfq1io'
# 応答:
{"message": "logout success"}JWT ペイロードには次のクレームが含まれます。
{
"jti": "xxxx",
"iss": "abcd",
"sub": "test",
"aud": "www.test.com",
"iat": 1665660527,
"exp": 1865673819
}key が ["jti"] に設定されている場合、ゲートウェイは Redis キー higress_jwt_logout_jti##xxxx を書き込みます。
ステップ 2: 失効したトークンが拒否されることの確認。
curl http://xxx.hello.com/test/abc \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJ4eHh4IiwiaXNzIjoiYWJjZCIsInN1YiI6InRlc3QiLCJhdWQiOiJ3d3cudGVzdC5jb20iLCJpYXQiOjE2NjU2NjA1MjcsImV4cCI6MTg2NTY3MzgxOX0.tmKF6qc1mOWNyCCzBOT2XKNoEGeEgr3EbhTKAQfq1io'
# 応答:
{"message":"invalid token"}ゲートウェイは Redis 内でキー higress_jwt_logout_jti##xxxx を見つけ、リクエストを拒否します。
ログアウトエンドポイントは、現在のリクエストに含まれるトークンのみを失効させます。別のトークンを失効させるには、上記で説明したキー形式を使用して、対応する Redis キーを手動で作成してください。
例: シングルデバイスログインの強制
アカウントを 1 つのアクティブセッションに制限し、セッションを新しいデバイスに強制的に切り替えます。
プラグイン構成:
redis:
service: redis.dns
port: 6379
jwks: |
{
"keys": [
{
"kty": "oct",
"kid": "123",
"k": "hM0k3AbXBPpKOGg__Ql2Obcq7s60myWDpbHXzgKUQdYo7YCRp0gUqkCnbGSvZ2rGEl4YFkKqIqW7mTHdj-bcqXpNr-NOznEyMpVPOIlqG_NWVC3dydBgcsIZIdD-MR2AQceEaxriPA_VmiUCwfwL2Bhs6_i7eolXoY11EapLQtutz0BV6ZxQQ4dYUmct--7PLNb4BWJyQeWu0QfbIthnvhYllyl2dgeLTEJT58wzFz5HeNMNz8ohY5K0XaKAe5cepryqoXLhA-V-O1OjSG8lCNdKS09OY6O0fkyweKEtuDfien5tHHSsHXoAxYEHPFcSRL4bFPLZ0orTt1_4zpyfew",
"alg": "HS256"
}
]
}
login:
path: "/jwt_login"
key: ["iss","aud","sub"]
error_status: 403
error_body: |
{"message":"already login on other device"}ステップ 1: 初回ログイン (自動登録)。
curl http://xxx.hello.com/test/abc \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJ6enp6IiwiaXNzIjoiYWJjZCIsImF1ZCI6Ind3dy5leGFtcGxlLmNvbSIsInN1YiI6InRlc3QiLCJpYXQiOjE2NjU2NjA1MjcsImV4cCI6MTg2NTY3MzgxOX0.WljMr5ucxfLF8SmeaaL25c0QG3IX04HoD0als9gglYg'JWT ペイロード:
{
"jti": "zzzz",
"iss": "abcd",
"aud": "www.example.com",
"sub": "test",
"iat": 1665660527,
"exp": 1865673819
}Redis に一致するキーが存在しないため、ゲートウェイは higress_jwt_login_iss#aud#sub##abcd#www.example.com#test を書き込み、リクエストを許可します。この JWT がアクティブセッションになります。
ステップ 2: 別のデバイスからのログイン試行。
同じ iss、aud、および sub の値を持つが、jti の値が異なる別の JWT(「yyyyy」ではなく「zzzz」):
curl http://xxx.hello.com/test/abc \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJqdGkiOiJ5eXl5eSIsImlzcyI6ImFiY2QiLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJ0ZXN0IiwiaWF0IjoxNjY1NjYwNTI5LCJleHAiOjE4NjU2NzM4MTl9.6vi6eKPWSKHQxfzBPrj3-SWI4Q5zGtWhqp38JIN3FEo'
# 応答:
{"message":"already login on other device"}ID フィールド(iss、aud、sub)は既存の Redis キーと一致しますが、JWT 全体が異なります。ゲートウェイはリクエストを拒否します。
ステップ 3: 新しいデバイスからの強制ログイン。
ログインパスサフィックスにリクエストを送信して、アクティブセッションを上書きします。
curl http://xxx.hello.com/test/jwt_login \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJqdGkiOiJ5eXl5eSIsImlzcyI6ImFiY2QiLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJ0ZXN0IiwiaWF0IjoxNjY1NjYwNTI5LCJleHAiOjE4NjU2NzM4MTl9.6vi6eKPWSKHQxfzBPrj3-SWI4Q5zGtWhqp38JIN3FEo'
# 応答:
{"message":"login success"}ゲートウェイは Redis の値を新しい JWT で上書きします。この後:
新しい JWT を使用したリクエストは成功します。
古い JWT (ステップ 1 からのもの) を使用したリクエストは、
{"message":"already login on other device"}で拒否されます。
有効な JWT を使用した最初のリクエストは、アクティブセッションとして自動的に登録されます。初回ログインのために強制ログインエンドポイントを呼び出す必要はありません。デバイス間でセッションを切り替える場合にのみ使用してください。
使用上の注意
デフォルトのログアウトキー
["jti"]は、RFC 7519 に従って各トークンを一意に識別するjti(JWT ID) クレームに依存します。2 つのトークンが同じjtiを共有する場合、それらは同一として扱われます。デフォルトのログインキー
["iss","aud","sub"]は、発行者、オーディエンス、およびサブジェクトを組み合わせたものであり、通常、一意のユーザーとアプリケーションのペアを表し、シングルデバイス強制に適しています。ログアウトをトリガーすると、同じ JWT に関連付けられたログインキーもクリアされます。これは、トークン失効、シングルデバイスログイン、またはその両方が有効になっているかどうかに関わらず適用されます。
HTTP ステータスコード
| ステータスコード | メッセージ | 原因 |
|---|---|---|
| 401 | invalid token | リクエストヘッダーに JWT がない、JWT 形式が無効、JWT の有効期限切れ、またはトークンが失効している。 |
| 500 | redis server error | Redis 接続がタイムアウトしたか、失敗しました。 |