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服務名稱。例如:
|
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
建立Redis執行個體。具體操作,請參見Redis 快速入門。
開啟logout和login配置。外掛程式將處理1次請求,導致產生3次Redis讀請求(1次logout檢查,2次login檢查)。在少量情況下(如登出或首次登入),將額外產生2次Redis寫請求。為評估Redis容量,可以簡單地按外掛程式處理的請求輸送量乘以2進行計算。
配置好Redis後,擷取其在VPC內的地址。例如:r-xxxxxxx.redis.rds.aliyuncs.com。
添加服務。服務來源選擇DNS網域名稱,服務連接埠填寫Redis連接埠(一般為6379),網域名稱列表中填寫擷取的VPC地址,TLS模式選擇關閉。具體操作,請參見建立服務。
在外掛程式配置中添加如下內容即可串連到該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"}觸發登出。
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。登出後,再使用該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"}成功登入。
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。相同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會被拒絕訪問。強制登入。
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逾時或失敗等原因。 |