把JWT
實作在route
上的部分講解過之後,接著就該進入到consumer
的使用階段了。這部分原本筆者打算使用postman
來進行示範,但細節實在太多了,寫成文章去說明太瑣碎。迫不得已的狀況下,筆者又另外寫了一個 open source project來示範,請讀者參考Kong Security JWT 示範專案。
這次的示範與與之前的案例一樣,請先到專案下將服務啟動,讀者可以在示範專案的以下路徑找到。
ironman2025\case_ELK_JPG_Route_JWT
docker compose up -d
圖28-1 服務啟動
首先先來驗證前一天的設定,確認是否有生效,筆者使用postman
來驗證,從圖28-2 可以確定JWT
已經生效在新的route
上了。
圖28-2 新route 與jwt 設定生效
既然已經確定設定沒有問題,接下來使用筆者寫的Kong Security JWT 示範專案分別對HS256
與RS256
的測試。
雖說readme.md
上有一些注意事項,但大多數筆者都已經準備好了,簡單說明如下:
- HS256的實驗,筆者的在後面的指令已經準備好
kong
設定的金鑰,但如果要做其他實驗,就需在kong
中設定一組長度超過 16 字元的 Consumer Secret。- RS256的金鑰,已經備在專案目錄下的
private_key.pem
(PEM 格式 RSA 私鑰)。requestUrl
變數預設為http://localhost:8000/FinancalJWT/2025-09-11
,如果依照筆者案例進行實驗,應該不會有問題。讀者亦可依自己的需求修改。
首先先來進行HS256
的實驗,請讀者到專案目錄Kong_Security_JWT
下,執行以下指令:
# 執行格式示範
# dotnet run -- HS256 <Consumer_key> <Consumer_secret> <maximum_expiration>
dotnet run -- HS256 HS256_Sam_Key HS256_Sam_Secret_HS256_Key_minimum_size_of_256 1800
圖28-3 HS256 回應正確
# HS256的JWT產出的內容
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSFMyNTZfU2FtX0tleSIsImlhdCI6MTc1OTEwNzA0NywiZXhwIjoxNzU5MTA4ODQ3fQ._sAAGP4fMqPTIQc9dNRVnhEnLqqNYTJOoM3cjatSxnI
圖28-3 可以看到完成了一次成功的請求,細節先不說明,接著進行第二個RS256
的實驗。
# 執行格式示範
# dotnet run -- RS256 <Consumer_key> <maximum_expiration>
dotnet run -- RS256 RS256_Sam_Key 1800
圖28-4 RS256 回應正確
# RS256的JWT產出的內容
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiUlMyNTZfU2FtX0tleSIsImlhdCI6MTc1OTEwNzY0OSwiZXhwIjoxNzU5MTA5NDQ5fQ.AC_eNec9EhO4JVDB4FG5E1-cixhrRog7ggV3rlQLvLLlo0KnpdCmRwjC68skEhd7VB9O49Ji4MJBZ53FEhcNdP3juFQS2FiFiUiSOXOYEy2qKLkttj8aaEMVS76THIfzMHuvyGVTerDhqfyGE-8RiX79cbIEfLE7KBVq53uRB4t_m31eTAOHDUwTGVWHLASuvUnEEmV2zcyURyGyXxKKlpRC2wwAhm1ZD740ak82ENjlVltwuyEhsvBuIGLhs73OWE3a7_eZTkxKML9yvmguR4Myh8mD6xRviooAXNEBzvZZpeOm_pVPPIc-o0jkxqBv2fqiKMBTJ7VAh0SBQxdNWg
太棒了,這表示筆者的在kong
上面的設定與client
的實作都正確。接下來將透過JWT
的內容,來與昨日在kong
上的設定進行說明。
還記得昨天筆者有特別對kong
的設定進行了一些重點式的說明嗎?簡單節錄一些關鍵點如下:
claims_to_verify: - exp
exp
(過期時間),Kong 會驗證 token 是否過期。header_names: - authorization
JWT
token 從 HTTP Header 的 Authorization
讀取。key_claim_name: name
maximum_expiration: 4800
讀者應該有注意到,前面的實驗,筆者將JWT
token都留在文章中了。這是為了在這部分講解JWT
token 的內容是如何與上述設定有直接的關聯。筆者將透過token.dev來重點說明JWT
token。
圖28-5 HS256 的JWT 拆解說明
從圖28-5 可以看出來,範例程式所產出的JWT
token中的所有細節。
Header
:指定使用了HS256
的加解密方式Payload
name
:HS256_Sam_Key
這則是kong
在consumer
的jwt_secrets
中,設定的key
值。這組key
值在設定route
時,被要求放在payload
的name
中。iat
:範例程式是使用執行時直接抓取當下時間,這時間不能早於kong
執行個體當下的時間,不然會被拒絕。exp
:
JWT
的逾時時間,由持有key
的簽發單位設定。範例程式的其中一個參數設定為1800
,這表示簽發出來的token 會根據當下時間iat
增加1800
秒作為這個token的過期時間(畫面上看到筆者在透過token.dev讀取時已經逾時)。kong
會根據這個時間來判定,是否已經逾時。另外在route
設定時,kong
也有要求最高僅能設定exp
與iat
時間最大不得超過4800
秒,以防有人產出超長不會過期的token
。註:
RS256
的JWT
token 筆者就不特別到token.dev截圖說明,留給讀者自己去閱讀看看。
從上面就可以看出,kong
在route
上面jwt
plugin的設定,與consumer
之間的關係了。kong
允許在consumer
的jwt_secrets
設定中,自由的允許多組key
存在,而且也可以根據需求使用對稱或是非對稱的金鑰。
可以同時存在多組key
這件事情,就回到hmac-auth
時談到的,可以用來確認consumer
在年度輪換金鑰時,是否已經使用新的金鑰來發動API的請求。
圖28-6 年度輪替金鑰可參考Kibana 中的請求資訊
雖說kong
的OpenID Connect 的plugin
需要企業版的授權,才可以使用,但其實OpenID Connect 的基礎是JWT
token。因此筆者這次稍微研究了一下(其實花了好多時間),才把如何透過kong
的JWT
plugin結合用來做驗證。
首先要來補充一下OpenID Connect的最基本知識。OpenID Connect(OIDC)是建立在 OAuth 2.0 基礎上的一個身份驗證協議,主要用於讓應用程式安全地驗證使用者身份,並取得基本的ID token (使用者資訊 Profile)或Access token(本次使用
)。
OIDC 規範要求每個授權伺服器都要提供一份公開的設定檔,路徑為:https://<domain>/.well-known/openid-configuration
。這份 JSON 文件描述了 OIDC 伺服器的端點、支援的功能、金鑰資訊等。
到目前為止,筆者將這次實驗要注意的部分,簡單列幾點說明如下:
不同於筆者前面做的實驗,需要自己產出各種公私鑰提供給consumer
使用,這次直接將認證(不自己產出公私鑰)的任務外包出去。這種作法其實是現在最主流的認證授權方式,集中在一個授權伺服器(Identity Provider, IDP),並透過該授權伺服器進行細節定義,如果有任何異常存取也可以快速透過IDP發現。
這次筆者使用EntraID
來進行示範,相關的APP Registration
、scope
以及使用者的設定就略過,因為步驟非常多。如果有讀者有興趣敲碗,可以留言給筆者,有機會筆者可以再另開一篇。
首先先看到筆者這次在EntraID 的API設定中,公開的.well-known/openid-configuration (連結點我)。
內容非常多,筆者僅截取這次要用到的兩個endpoint
{
"token_endpoint": "https://login.microsoftonline.com/c5f10cdb-152a-47ca-8082-cf4f80844b3f/oauth2/v2.0/token",
...中略...
"jwks_uri": "https://login.microsoftonline.com/c5f10cdb-152a-47ca-8082-cf4f80844b3f/discovery/v2.0/keys",
....下略...
第一個token_endpoint
是用來讓consumer
根據IDP
提供的認證方式,取回access token
的endpoint。但要如何與這個token_endpoint
進行互動後取回access token
可以參閱連結 Microsoft identity platform and the OAuth 2.0 client credentials flow - get-a-token 章節,筆者等等會直接透過postman
實作。
第二個jwks_uri
則是公開公鑰的endpoint,該網址提供一份 JWKS(JSON Web Key Set),也就是一組公開金鑰(Public Keys)。驗證端(如 API Gateway、應用程式)如果支援OIDC協議,就會自動從這個網址下載公鑰,來驗證 JWT 的 RS256 簽章。不過由於kong
open source 版本並沒有支援 oidc
協議,因此筆者透過手動的方式,將公開金鑰去回後,存入 kong.yml
的設定中。
圖28-7 從EetraID 取回access token
可以注意到
username
是昨天筆者在設定consumer username
的時候所使用的@samnchiuhotmail.onmicrosoft.com
帳號。這其實是consumer
要與IDP認證用的帳號。在kong
的設定中,可以用來識別consumer
的身分。
筆者首先先到第一個endpoint,取回access token
後,到token.dev
先做解析,這次有兩段重點如圖28-8。
圖28-8 解開access token
Header
的部分先關注到kid
這個關鍵字,這是用來找尋jwks_uri
的endpoint中,眾多公鑰指定的那把。另外Payload
的name
這個欄位,則是consumer
登入到EntraID
中所註冊的name
,同樣的也會被用來做為kong
consumer 的 jwt 設定中的key
識別。
接下來,就是到https://login.microsoftonline.com/c5f10cdb-152a-47ca-8082-cf4f80844b3f/discovery/v2.0/keys
中找出kid=HS23b7Do7TcaU1RoLHwpIq24VYg
的公鑰,可以參考圖28-9。
圖28-9 JWK
在搜尋之下,找到了公開金鑰,但這是JWK
格式,而kong
僅支援PEM
格式,因此筆者找了一個線上的網站,協助將公開金鑰從JWK
轉換成PEM
(參考8gwifi.org)。
圖28-10 JWT to PEM
如同前面的實驗一樣,當取得了public key
,就可以設定在kong
的consumer
中,用來驗證jwt
是否合法,因此將其設定在kong.yml
中。
- algorithm: RS256
key: sam
rsa_public_key: |-
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiJziawWRBqrsePVXdine
PiauatgEdl0iljqrr72Eu4T1qVbbA8BfDxXGW95ZFaq/roTTrnU2LEGQJ0jof6TS
A1VwTsvA5oRb57kW3On520ckieVgP1NFU5kVACk7KWNgAlBoTRCS5LvTYjX+0Ke/
L9wG36tL81EBimevhFCs/Ay2QIB07OI0YIhph/Pt94+isXPntLzwEyFQhRZYeRwL
JkjQGkkSyduLelMULmLirJ7sY/5ek90JALLt/gNCP+kH4jvFkFKOnB4w/uBR6AEf
Byy+JoCDI4HJAtpgn3ZrX03XnqHbQ1D8ddQMbdvAHYdWyiQYWttxtvwRduHM6fF1
mwIDAQAB
-----END PUBLIC KEY-----
secret: xxx
接著別忘了重新啟動kong
,讓設定可以生效。
一切都就緒了,筆者先來畫一張示意圖,請參考圖28-11。
圖28-11 離線版OIDC
圖28-11中可以看到,筆者將流程列點,簡單說明如下:
OIDC
中的公開金鑰,並設定於kong
之中。consumer
透過ID/PW/ClientID/Secret 至傳送至IDP(EntraID)
。IDP
取回access token
。access token
帶入在請求的標頭(authorization
:bearer
access token
)。kong
根據公鑰檢驗access token
是否合法。api
。200
,回應api
內容。
圖 28-12 正確回應200
圖28-12 則表明了,這整個流程是可以完成驗證的,這樣就完成了一次OIDC(低配版?)的認證授權程序。
Lala:看起來使用RS256
的方式來簽發JWT
,就可以滿足非對稱金鑰的要求了。
Sam:沒錯,這種作法相信可以滿足客戶的需求。
Aries:不能使用OIDC
感覺有點可惜,但畢竟是企業版才有的功能。不過既然是非對稱金鑰,而且私鑰是由consumer
持有,那是不是請對方自行簽發並提供公鑰給我們比較恰當?不然如果由我們配發私鑰與設定公鑰,那不就代表我們持有對方私鑰了?
Sam:我持有不同看法,原因是我們準備透過kong
來保護我方的api 資產,即使一開始我們持有對方私鑰,也僅能用來存取我方本來就持有的資產。再者如果我方人員存有惡意,那也不需透過對方的私鑰才能存取api,直接在設定層面改掉公鑰不就好了?
Aries:我懂了,所以是基於誰是被存取資源被保護為前提去思考,所以我方配發公私鑰不會有太多議題才對。
Sam:你說對了。
於是團隊又快樂的完成了本次的任務,並隨時準備迎接下一個挑戰。
接著最後的兩天,筆者打算撰寫關於藍綠佈署的應用,請讀者期待,明天見~