iT邦幫忙

2025 iThome 鐵人賽

DAY 28
0
DevOps

解鎖API超能力:我的30天Kong可觀測性與管理實戰之旅系列 第 28

Day 28 : Kong 的 JWT 實踐 - 認證實作及探索OIDC的流程

  • 分享至 

  • xImage
  •  

Kong 的Consumer JWT實作

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上了。

新route 與jwt 設定生效
圖28-2 新route 與jwt 設定生效

既然已經確定設定沒有問題,接下來使用筆者寫的Kong Security JWT 示範專案分別對HS256RS256的測試。

雖說readme.md上有一些注意事項,但大多數筆者都已經準備好了,簡單說明如下:

  • HS256的實驗,筆者的在後面的指令已經準備好kong設定的金鑰,但如果要做其他實驗,就需在kong中設定一組長度超過 16 字元的 Consumer Secret。
  • RS256的金鑰,已經備在專案目錄下的 private_key.pem(PEM 格式 RSA 私鑰)。
  • requestUrl 變數預設為 http://localhost:8000/FinancalJWT/2025-09-11,如果依照筆者案例進行實驗,應該不會有問題。讀者亦可依自己的需求修改。

HS256 與 RS256 的實驗

首先先來進行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

HS256 回應正確
圖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

RS256 回應正確
圖28-4 RS256 回應正確

# RS256的JWT產出的內容
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiUlMyNTZfU2FtX0tleSIsImlhdCI6MTc1OTEwNzY0OSwiZXhwIjoxNzU5MTA5NDQ5fQ.AC_eNec9EhO4JVDB4FG5E1-cixhrRog7ggV3rlQLvLLlo0KnpdCmRwjC68skEhd7VB9O49Ji4MJBZ53FEhcNdP3juFQS2FiFiUiSOXOYEy2qKLkttj8aaEMVS76THIfzMHuvyGVTerDhqfyGE-8RiX79cbIEfLE7KBVq53uRB4t_m31eTAOHDUwTGVWHLASuvUnEEmV2zcyURyGyXxKKlpRC2wwAhm1ZD740ak82ENjlVltwuyEhsvBuIGLhs73OWE3a7_eZTkxKML9yvmguR4Myh8mD6xRviooAXNEBzvZZpeOm_pVPPIc-o0jkxqBv2fqiKMBTJ7VAh0SBQxdNWg

太棒了,這表示筆者的在kong上面的設定與client的實作都正確。接下來將透過JWT的內容,來與昨日在kong上的設定進行說明。

JWT 的內容與kong之間的關係

還記得昨天筆者有特別對kong的設定進行了一些重點式的說明嗎?簡單節錄一些關鍵點如下:

  • claims_to_verify: - exp
    • exp(過期時間),Kong 會驗證 token 是否過期。
  • header_names: - authorization
    • JWT token 從 HTTP Header 的 Authorization 讀取。
  • key_claim_name: name
    • 這是 JWT payload 裡用來對應 Kong Consumer 中jwt_secrets的key欄。
  • maximum_expiration: 4800
    • Token 最長有效時間(秒數)。

讀者應該有注意到,前面的實驗,筆者將JWT token都留在文章中了。這是為了在這部分講解JWT token 的內容是如何與上述設定有直接的關聯。筆者將透過token.dev來重點說明JWT token。

HS256 的JWT 拆解說明
圖28-5 HS256 的JWT 拆解說明

從圖28-5 可以看出來,範例程式所產出的JWT token中的所有細節。

  • Header:指定使用了HS256的加解密方式
  • Payload
    • nameHS256_Sam_Key這則是kongconsumerjwt_secrets中,設定的key值。這組key值在設定route時,被要求放在payloadname中。
    • iat:範例程式是使用執行時直接抓取當下時間,這時間不能早於kong執行個體當下的時間,不然會被拒絕。
    • exp
      • 這是設定這組JWT的逾時時間,由持有key的簽發單位設定。範例程式的其中一個參數設定為1800,這表示簽發出來的token 會根據當下時間iat增加1800秒作為這個token的過期時間(畫面上看到筆者在透過token.dev讀取時已經逾時)。
      • kong 會根據這個時間來判定,是否已經逾時。另外在route設定時,kong也有要求最高僅能設定expiat時間最大不得超過4800秒,以防有人產出超長不會過期的token

註:RS256JWT token 筆者就不特別到token.dev截圖說明,留給讀者自己去閱讀看看。

從上面就可以看出,kongroute上面jwt plugin的設定,與consumer之間的關係了。kong允許在consumerjwt_secrets設定中,自由的允許多組key存在,而且也可以根據需求使用對稱或是非對稱的金鑰。

可以同時存在多組key這件事情,就回到hmac-auth時談到的,可以用來確認consumer在年度輪換金鑰時,是否已經使用新的金鑰來發動API的請求。

年度輪替金鑰可參考Kibana 中的請求資訊
圖28-6 年度輪替金鑰可參考Kibana 中的請求資訊

補充:OpenID Connect 中的JWT 實作

雖說kong的OpenID Connect 的plugin需要企業版的授權,才可以使用,但其實OpenID Connect 的基礎是JWT token。因此筆者這次稍微研究了一下(其實花了好多時間),才把如何透過kongJWT plugin結合用來做驗證。

首先要來補充一下OpenID Connect的最基本知識。OpenID Connect(OIDC)是建立在 OAuth 2.0 基礎上的一個身份驗證協議,主要用於讓應用程式安全地驗證使用者身份,並取得基本的ID token (使用者資訊 Profile)或Access token(本次使用)。

OIDC 規範要求每個授權伺服器都要提供一份公開的設定檔,路徑為:https://<domain>/.well-known/openid-configuration。這份 JSON 文件描述了 OIDC 伺服器的端點、支援的功能、金鑰資訊等。

到目前為止,筆者將這次實驗要注意的部分,簡單列幾點說明如下:

  • OIDC的基於OAuth 2.0,可以取得token。
  • 授權伺服器有一個公開的設定檔文件,上面記載了各種端點。

不同於筆者前面做的實驗,需要自己產出各種公私鑰提供給consumer使用,這次直接將認證(不自己產出公私鑰)的任務外包出去。這種作法其實是現在最主流的認證授權方式,集中在一個授權伺服器(Identity Provider, IDP),並透過該授權伺服器進行細節定義,如果有任何異常存取也可以快速透過IDP發現。

這次筆者使用EntraID來進行示範,相關的APP Registrationscope以及使用者的設定就略過,因為步驟非常多。如果有讀者有興趣敲碗,可以留言給筆者,有機會筆者可以再另開一篇。

首先先看到筆者這次在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的設定中。

第一步 取回access token

從EentraID 取回access token
圖28-7 從EetraID 取回access token

可以注意到username是昨天筆者在設定consumer username的時候所使用的@samnchiuhotmail.onmicrosoft.com帳號。這其實是consumer要與IDP認證用的帳號。在kong的設定中,可以用來識別consumer的身分。

筆者首先先到第一個endpoint,取回access token後,到token.dev先做解析,這次有兩段重點如圖28-8。

解開access token
圖28-8 解開access token

Header的部分先關注到kid這個關鍵字,這是用來找尋jwks_uri的endpoint中,眾多公鑰指定的那把。另外Payloadname這個欄位,則是consumer登入到EntraID中所註冊的name,同樣的也會被用來做為kong consumer 的 jwt 設定中的key識別。

第二步 找出public key 並設定到 kong

接下來,就是到https://login.microsoftonline.com/c5f10cdb-152a-47ca-8082-cf4f80844b3f/discovery/v2.0/keys中找出kid=HS23b7Do7TcaU1RoLHwpIq24VYg的公鑰,可以參考圖28-9。

JWK
圖28-9 JWK

在搜尋之下,找到了公開金鑰,但這是JWK格式,而kong僅支援PEM格式,因此筆者找了一個線上的網站,協助將公開金鑰從JWK轉換成PEM(參考8gwifi.org)。

JWT to PEM
圖28-10 JWT to PEM

如同前面的實驗一樣,當取得了public key,就可以設定在kongconsumer中,用來驗證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。

離線版OIDC
圖28-11 離線版OIDC

圖28-11中可以看到,筆者將流程列點,簡單說明如下:

  1. 先找到OIDC中的公開金鑰,並設定於kong之中。
  2. 則是consumer透過ID/PW/ClientID/Secret 至傳送至IDP(EntraID)
  3. IDP取回access token
  4. 發送請求,並將access token帶入在請求的標頭(authorization:bearer access token)。
  5. kong根據公鑰檢驗access token是否合法。
  6. 確認合法,傳送至上游api
  7. 狀態200,回應api內容。

正確回應200
圖 28-12 正確回應200

圖28-12 則表明了,這整個流程是可以完成驗證的,這樣就完成了一次OIDC(低配版?)的認證授權程序。

小結

Lala:看起來使用RS256的方式來簽發JWT,就可以滿足非對稱金鑰的要求了。

Sam:沒錯,這種作法相信可以滿足客戶的需求。

Aries:不能使用OIDC感覺有點可惜,但畢竟是企業版才有的功能。不過既然是非對稱金鑰,而且私鑰是由consumer持有,那是不是請對方自行簽發並提供公鑰給我們比較恰當?不然如果由我們配發私鑰與設定公鑰,那不就代表我們持有對方私鑰了?

Sam:我持有不同看法,原因是我們準備透過kong來保護我方的api 資產,即使一開始我們持有對方私鑰,也僅能用來存取我方本來就持有的資產。再者如果我方人員存有惡意,那也不需透過對方的私鑰才能存取api,直接在設定層面改掉公鑰不就好了?

Aries:我懂了,所以是基於誰是被存取資源被保護為前提去思考,所以我方配發公私鑰不會有太多議題才對。

Sam:你說對了。

於是團隊又快樂的完成了本次的任務,並隨時準備迎接下一個挑戰。

接著最後的兩天,筆者打算撰寫關於藍綠佈署的應用,請讀者期待,明天見~


上一篇
Day 27 : Kong 的 JWT 實踐 - 對稱與非對稱金鑰介紹
下一篇
Day 29 : 透過 Azure DevOps 實踐 Kong 的藍綠佈署 - 1
系列文
解鎖API超能力:我的30天Kong可觀測性與管理實戰之旅29
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言