全部產品
Search
文件中心

API Gateway:jwt-logout外掛程式

更新時間:Mar 10, 2025

jwt-logout外掛程式利用Redis實現了對JWT的弱狀態管理機制,解決了JWT本身無法主動登出的問題,並可用於實現唯一登入控制,例如在支援多裝置登入情境下實現使用者互踢功能。

外掛程式類型

認證鑒權。

配置欄位

名稱

資料類型

填寫要求

預設值

描述

jwks

string

選填

-

指定JSON字串,用於驗證JWT。當此外掛程式搭配jwt-auth外掛程式使用時,無需配置此項。更多資訊,請參見詳情

clock_skew

number

選填

60

校正JWT的exp和iat欄位時,允許的時鐘位移量。單位為秒。

token_header

string

選填

Authorization

抽取JWT的請求Header。

token_prefix

string

選填

"Bearer"

將請求Header中Value值的指定首碼去除後,剩餘部分將被用作JWT的內容。

redis

Redis

必填

-

Redis服務配置資訊。

logout

Logout

選填

-

用於實現JWT登出功能的配置。不配置此欄位時,該功能關閉。

login

Login

選填

-

用於實現JWT唯一登入的配置。不配置此欄位時,該功能關閉。

Redis類型的配置欄位說明如下:

名稱

資料類型

填寫要求

預設值

描述

service

string

必填

-

Redis服務名稱。例如:

  • 固定地址服務:my-redis.static。

  • DNS網域名稱服務 (DNS):my-redis.dns。

  • K8sContainer Service:my-redis.default.svc.cluster.local。

port

number

必填

-

Redis服務連接埠。

username

string

選填

-

Redis AUTH命令使用的username。

password

string

選填

-

Redis AUTH命令使用的password。

timeout

number

選填

1000

Redis命令的逾時時間。單位為毫秒。

Logout類型的配置欄位說明如下:

名稱

資料類型

填寫要求

預設值

描述

key_prefix

string

選填

higress_jwt_logout_

Redis中儲存Key的首碼。

key

array of string

選填

["jti"]

指定JWT Payload中的欄位標識JWT,攜帶相同欄位的會被認作同一個JWT;若JWT Payload中不存在相應欄位,將返回401 invalid token。

path

string

選填

/jwt_logout

URL路徑尾碼匹配此字串時,登出當前請求攜帶的JWT,該JWT此後將無法使用。

error_status

number

選填

401

登出狀態時的錯誤狀態代碼。

error_body

string

選填

'{"message":"invalid token"}'

登出狀態時的響應Body。

ttl

number

選填

-

在Redis中,儲存Key的ttl,以秒為單位。此配置確定了登出的JWT在多長時間內無法使用。若未填寫此配置,將根據Payload中的exp減去目前時間來計算存留時間;若Payload中沒有exp欄位,則預設的ttl為86400秒(24小時)。

Login類型的配置欄位說明如下:

名稱

資料類型

填寫要求

預設值

描述

key_prefix

string

選填

higress_jwt_logout_

Redis中儲存key的首碼。

key

array of string

選填

["iss","aud","sub"]

指定JWT Payload中的欄位作為唯一登入標識。若攜帶相同欄位但與已登入的JWT不完全相同,將被判斷為重複登入並拒絕訪問,直到已登入的JWT對應的key在Redis中到期;若JWT Payload中不存在相應欄位,將返回401 invalid token。

path

string

選填

/jwt_login

URL路徑尾碼匹配此字串時,強制登入當前請求攜帶的JWT,已登入的相同Payload特徵的JWT將被登出。

error_status

number

選填

403

重複登入時的錯誤狀態代碼。

error_body

string

選填

'{"message":"already login on other device"}'

重複登入時的響應Body。

ttl

number

選填

-

在Redis中儲存key的ttl,單位為秒,決定唯一登入的JWT在多久時間內有效。若未填寫此配置,則基於Payload中的exp減去目前時間計算。若Payload中未包含exp欄位,則預設ttl為86400秒(24小時)。

配置樣本

使用阿里雲Redis

  1. 建立Redis執行個體。具體操作,請參見Redis 快速入門

  2. 開啟logout和login配置。外掛程式將處理1次請求,導致產生3次Redis讀請求(1次logout檢查,2次login檢查)。在少量情況下(如登出或首次登入),將額外產生2次Redis寫請求。為評估Redis容量,可以簡單地按外掛程式處理的請求輸送量乘以2進行計算。

  3. 配置好Redis後,擷取其在VPC內的地址。例如:r-xxxxxxx.redis.rds.aliyuncs.com。

  4. 添加服務。服務來源選擇DNS網域名稱,服務連接埠填寫Redis連接埠(一般為6379),網域名稱列表中填寫擷取的VPC地址,TLS模式選擇關閉。具體操作,請參見建立服務

  5. 在外掛程式配置中添加如下內容即可串連到該Redis:

    redis:
      service: redis.dns
      port: 6379

    如果給Redis服務設定了密碼,則配置如下:

    redis:
      service: redis.dns
      port: 6379
      password: ****** # 這裡填寫您設定的密碼

實現JWT登出

情境介紹

由於JWT是面向無狀態設計的,一旦簽發就會一直有效,直到JWT到期。基於這一外掛程式的能力,可以實現對指定JWT的強制登出。

實現原理

  • 若請求路徑的尾碼與外掛程式配置中的Path匹配,則會觸發登出機制,通過Redis記錄需要登出的JWT。Redis中儲存的Key由配置的首碼以及從當前JWT Payload中提取出的Key和Value構成。

  • 若請求中攜帶的JWT的Payload與Redis中儲存的Key特徵相匹配,則會被視為當前JWT已經登出,並將拒絕訪問。

  • Redis中鍵的到期時間預設值是基於JWT Payload的exp欄位計算的,即預設儲存到該JWT到期。

說明
  • 外掛程式預設推薦的登出鍵是["jti"],jti是用於唯一標識一個JWT的Payload欄位。按照JWT標準要求,當jti相同時,整個JWT也是完全相同的,因此可用來標記JWT的登出狀態。

  • Redis中儲存鍵的拼接規則為:<key_prefix><PayloadKey>##<PayloadValue>,其中PayloadKey由多個鍵用#間隔拼接,PayloadValue由相應鍵對應的值用#間隔拼接。例如:higress_jwt_logout_jti#iss##xxxxx#abcde

  • 本外掛程式只能登出當前請求攜帶的Token,您也可以手動操作Redis來登出指定的Token,按照上述鍵拼接規則即可。當網關查詢Redis發現該鍵存在時,即會拒絕相應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"}

    該Token的Payload為:

    {
        "jti": "xxxx",
        "iss": "abcd",
        "sub": "test",
        "aud": "www.test.com",
        "iat": 1665660527,
        "exp": 1865673819
    }

    此時Redis中將存在一個Key:higress_jwt_logout_jti##xxxx

  2. 登出後,再使用該JWT,將無法訪問。

    curl  http://xxx.hello.com/test/abc -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJ4eHh4IiwiaXNzIjoiYWJjZCIsInN1YiI6InRlc3QiLCJhdWQiOiJ3d3cudGVzdC5jb20iLCJpYXQiOjE2NjU2NjA1MjcsImV4cCI6MTg2NTY3MzgxOX0.tmKF6qc1mOWNyCCzBOT2XKNoEGeEgr3EbhTKAQfq1io'
    
    # 將返回以下應答:
    {"message":"invalid token"}

實現JWT登入互踢

情境介紹

當使用者在多個裝置上登入,並使用JWT進行登入認證時,可能會在不同的裝置上籤發不同的JWT。通過此外掛程式,可以實現使用者只能在同一時間在一個裝置上登入。

實現原理

  • 在請求時,提取當前請求的JWT,並根據配置的首碼以及從當前JWT Payload中提取出的Key和Value拼接出Redis Key。接著,在Redis中查詢該Key所對應的Value,若該Value與當前JWT不一致,則拒絕訪問。

  • 若該Value不存在,則將當前JWT寫入該Redis Key。Redis中Key的到期時間預設值是基於JWT Payload的exp欄位計算的,即預設儲存到該JWT到期。在此期間,具備相同Payload特徵的其他JWT均將無法訪問。

  • 若請求的尾碼匹配外掛程式配置中的path,則觸發強制登入機制,會將當前JWT寫入對應的Redis Key的Value。這意味著之前已經登入的具有相同Payload特徵的JWT將被登出,實現了登入時互踢的效果。

說明
  • 外掛程式預設推薦的login key是["iss","aud","sub"],其中iss代表JWT的簽發者,aud代表JWT的使用情境,sub代表JWT的簽發對象。通常情況下,這三者的組合可以滿足登入時互踢的情境。

  • Redis中儲存Key的拼接規則為:<key_prefix><PayloadKey>##<PayloadValue>,其中PayloadKey由多個Key用#間隔拼接,PayloadValue由Key對應的Value用#間隔拼接。舉例:higress_jwt_login_iss#aud#sub##xxxxx#abcde#fffff

  • 在相同Payload特徵的情況下,首個JWT會隨業務請求自動寫入Redis,從而實現外掛程式的唯一登入功能。因此,不需要調用強制登入介面。只有在登入時需要進行互踢的情況下,才需要使用該介面。

  • 就像同時開啟了JWT logout功能一樣,當觸發logout邏輯時,也會清理Redis中當前JWT的login key。

樣本

外掛程式配置:

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'

    該Token的Payload為:

    {
        "jti": "zzzz",
        "iss": "abcd",
        "aud": "www.example.com",
        "sub": "test",
        "iat": 1665660527,
        "exp": 1865673819
    }

    此時Redis中將存在一個Key:higress_jwt_login_iss#aud#sub##abcd#www.example.com#abcd,Value為當前JWT。

  2. 相同Payload特徵拒絕訪問。

    curl  http://xxx.hello.com/test/abc -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJqdGkiOiJ5eXl5eSIsImlzcyI6ImFiY2QiLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJ0ZXN0IiwiaWF0IjoxNjY1NjYwNTI5LCJleHAiOjE4NjU2NzM4MTl9.6vi6eKPWSKHQxfzBPrj3-SWI4Q5zGtWhqp38JIN3FEo'
    
    # 將返回以下應答:
    {"message":"already login on other device"}

    該Token的Payload為:

    {
        "jti": "yyyyy",
        "iss": "abcd",
        "aud": "www.example.com",
        "sub": "test",
        "iat": 1665660527,
        "exp": 1865673819
    }

    和第一步中的JWT的Payload特徵一致,但非特徵欄位jti不同,因此該JWT會被拒絕訪問。

  3. 強制登入。

    curl  http://xxx.hello.com/test/jwt_login -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJqdGkiOiJ5eXl5eSIsImlzcyI6ImFiY2QiLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJ0ZXN0IiwiaWF0IjoxNjY1NjYwNTI5LCJleHAiOjE4NjU2NzM4MTl9.6vi6eKPWSKHQxfzBPrj3-SWI4Q5zGtWhqp38JIN3FEo'
    
    # 將返回以下應答:
    {"message":"login success"}

    此時Redis中已有的Key:higress_jwt_login_iss#aud#sub##abcd#www.example.com#abcd,其Value會被替換為當前JWT。

    使用當前JWT可以訪問成功:

    curl  http://xxx.hello.com/test/abc -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJqdGkiOiJ5eXl5eSIsImlzcyI6ImFiY2QiLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJ0ZXN0IiwiaWF0IjoxNjY1NjYwNTI5LCJleHAiOjE4NjU2NzM4MTl9.6vi6eKPWSKHQxfzBPrj3-SWI4Q5zGtWhqp38JIN3FEo'

    使用第一步的JWT訪問,將返回失敗:

    curl  http://xxx.hello.com/test/abc -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJqdGkiOiJ4eHh4IiwiaXNzIjoiYWJjZCIsImF1ZCI6Ind3dy5leGFtcGxlLmNvbSIsInN1YiI6InRlc3QiLCJpYXQiOjE2NjU2NjA1MjcsImV4cCI6MTg2NTY3MzgxOX0.P0WtBTHJzUJvklu9q8XSRszfPbgojrZHg7t4ZaYfKGo'
    
    # 將返回以下應答:
    {"message":"already login on other device"}

相關錯誤碼

HTTP 狀態代碼

出錯資訊

原因說明

401

invalid token

要求標頭未提供JWT、JWT格式錯誤或到期等原因。

500

redis server error

訪問Redis逾時或失敗等原因。