iT邦幫忙

2022 iThome 鐵人賽

DAY 14
1
Software Development

30 天與九頭蛇先生做好朋友系列 第 14

如何驗證 ID Token 的資訊

  • 分享至 

  • xImage
  •  

昨天我們完成了授權流程,並取到了 Access TokenID Token,但到這個階段,我們依然還不知道使用者是誰,今天的目的是要來解析 Token 背後所藏的使用者資訊。

雖然現階段我們可以在 Login Provider 上利用 Cookie 或 Session 之類的方法存放使用者資訊,然後在回到應用程式的時候再取得。但先前有提到 Hydra 的設計是將處理 OpenID Connect 流程(指 OP)、身分驗證和授權,三個不同目的分成不同的服務來處理,應用程式則又是另外一個,這四個服務有可能會部署在不同的網域,因此是無法透過 Cookie 或 Session 取得的。這也是為什麼 OpenID Connect 或 OAuth 2.0 的流程基礎,是透過轉導來傳遞授權請求與回應的;Hydra 在控制 Login Provider 與 Consent Provider 也是透過轉導來完成登入請求與同意請求,因為 Hydra 從一開始設計的出發點,就是將它們當作是不同的網域來處理的。

而回到一開始的問題,授權流程執行到最後,應用程式該如何取得使用者資訊呢?這些資訊其實是藏在 Token 裡面的。這裡先簡單說明,Token 有分成兩類,一個是不透明的 Token(Opaque Tokens),指的是 Token 是純粹無意義的亂數字串,必須從其他資料庫取得字串對應到資料庫裡的資訊;另一種是透明的 Token(Not Opaque Tokens),指的是 Token 有辦法透過演算法解析出內容並閱讀。剛好 Access Token 與 ID Token 分別對應到這兩類。

今天先來介紹屬於透明 Token 的 ID Token,明天則會介紹 Access Token 的驗證方法,後天再實際撰寫程式。

ID Token 的結構

ID Token 本身的格式是 JWT,昨天最終拿到的 ID Token 樣貌如下:

eyJhbGciOiJSUzI1NiIsImtpZCI6InB1YmxpYzpoeWRyYS5vcGVuaWQuaWQtdG9rZW4iLCJ0eXAiOiJKV1QifQ.eyJhdF9oYXNoIjoiVTMxN2xqVE5zMTBBV3Z3bmgwUW9IZyIsImF1ZCI6WyJteS1ycCJdLCJhdXRoX3RpbWUiOjE2NjQzNTE0MzcsImV4cCI6MTY2NDM1NTAzOSwiaWF0IjoxNjY0MzUxNDM5LCJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjQ0NDQvIiwianRpIjoiMjEwZWRhNDMtZjhjNy00NWEzLWEyNWItYTJiZDkyMjcyOTcxIiwicmF0IjoxNjY0MzUxNDMxLCJzaWQiOiI3MTViNjNjNC0wZjJhLTRmZTUtOGQyOC1lODMwZGQzNjEzNDkiLCJzdWIiOiIxIn0.sM3A5wLTUBE1DlDAqfcF8pSUNU74AaP2GDaJ_gLLEV_uh-tG6b6B8e58fxH1vRmjtTE6aJG-chG98di12paDD8tV6JXAcuyk3p5Tw_OZnCcFVEvAYoP_m8BYhwGK7DfmiWCZVMTHLX2AFd3J93XR7tems-xV1ponXQQaPDIVKjEVe3k9aoPkgKFj-8CabMwq-aVzKmq0Zr940tynWUTbu_ylSdnnvw68q74zmL6mwhGaDQ0DKTza-fDy_dp_Ht1QujIa-aSrLLkWm9MP0zzPCiZ1YYv-cpx3xb-tpoJm3bNu1h6XMGCjsoPtBOgQzESqojea0H2Cc7DsnDlezIVKM2OphF0_REy5ljBIA3zmbN0arbVVozN2p68YHX8YVp121yWpLycw1DA9eXdu2xp4I8N2Vmng9sez7KT8vKT9vCzFJgqTNG5Jhrv2VH7F4IXzjmbLD3bQW9HAtI_SGwSZ9FsQFUifxxgs1L9FFGb_pyc9arfoTqJnIaZgfsPMOQ6_4S4GlNwvX9YugOugecQfnfXX17ddNHtU7_YOkAAccZVaG5xQZEDtnggrtOVhBKebt5oHFLNDk44eK6GzD1yV9RtO0g0IK6OTt_IFN6gcEPo5d-QTphLxpYecLSvcYciI5k_wx-RpwZdJM1IU-JHqMUdHHm-DpOPCHzTAuXu894w

我們可以把這個 JWT 拿去 jwt.io 所提供的線上工具解析,即可看到右邊會出現對應的內容:

14-1.png

我們可以看到 JWT 解析的時候,是把字串切成三段資訊,對應到右邊的三個區塊,分別是 Header、Payload、Signature。因為 Header 與 Payload 的資訊解析只是單純的 Base 64 解碼和 JSON 解碼,所以網頁的 Javascript 可以很輕易地處理。而 Signature 的區塊的資訊則是我們要填的,它的輸入框有說明,上面是公鑰,下面是私鑰。

公私鑰是成對的,只有同一組的公私鑰才能正常運作。JWT 公私鑰運作的方法,公鑰是拿來驗證,私鑰是產生 JWT。這個設計的目的是,私鑰保存授權伺服器上並安全地保管,然後再把公鑰公開讓應用程式下載,接著由授權伺服器產生 JWT,只要應用程式驗證合法,即可證明這個 JWT 是由該授權伺服器所產生的。

驗證 JWT 簽章

接下來,因為驗證 JWT 需要公鑰,所以 Hydra 有提供下載方法--打 API:

http://127.0.0.1:4444/.well-known/jwks.json

API 會回傳 JSON 資訊,裡面是有一堆公鑰(指 keys)的結構,不過目前練習的情境只會拿到一把,那把的長相可能會像下面這樣::

{
  "use": "sig",
  "kty": "RSA",
  "kid": "public:hydra.openid.id-token",
  "alg": "RS256",
  "n": "3fLBH5AZuoJurOEDA8_MAodU9slUs7AQaeus3C6C7JdSpo7JjgyNMgNV5Fnu53gQlY3Pr5ZyWpfmzJwIFRLrfvT-iQcktXjnZIcFvkX67nAwoUiqBoppprQyTju56ZxrAZnLLr8CYpaDKIjrJkFQw5BWX2X00DIo_YjG_2AJkdlxGuCtFhaUl0VpPr7PmVTxroscagtWdRbb6bitwlkcyc-0ESP2NRIWp2erQ5FJeigPtyGfqSpXUAFbgfz3-koTBpcyf73FRc3BqkuOmAsUJWHl-7s9u8pDK_H9dq-Cg_hWqGohWc_oaA0_01-um647xkMvm4FLA4UH-h1pOiZoL5hyqNGF3FRcBoOLJcFqb4P3zq22sW28dluEEht2_WV3nxAHttHD3Sxbq4uMtjVucBjTwS8x4EVUvipqQ8z-jV386v9bG2xvx6KgUEMyPOsSAYI6ww6HDrlDHBXi1Fr0x7b9bPvlJe9MtLEvFTMe8UgmrcXOJO-xu4EN5HwH6wtnnnsYuw-0duiLL0mvE0AeXZurQy_u_vbh-thkTLkdQFBY93cY3yLcp0sll2FpXSrGNtZddX3x4yIDMQLbYqUzybiVbsohhu7xSYowTX77xIZobGxnuNpbGa857RD9zox9ugSh59Yq9qr4TC2DLAunXQEaalijUjr4sYIV6NCtrRk",
  "e": "AQAB"
}

欄位細節定義可以在 RFC 7517 - JSON Web Key (JWK) 裡找的到,這裡就不多做說明。使用上,可以將這個 JSON 字串當作是「一把公鑰」,然後把公鑰複製起來放進 JWT Debugger 的公鑰裡,就會看到左下角出現已驗證的訊息。

14-2.png

當簽章驗證成功後,代表 Header 與 Payload 的內容是可以信任的,因此下一步就是再來驗證 Payload 的內容是否合法。

驗證 JWT 內容

JWT 的 Payload 是主要實際要傳輸的內容,以上面範例內容如下:

{
  "at_hash": "U317ljTNs10AWvwnh0QoHg",
  "aud": [
    "my-rp"
  ],
  "auth_time": 1664351437,
  "exp": 1664355039,
  "iat": 1664351439,
  "iss": "http://127.0.0.1:4444/",
  "jti": "210eda43-f8c7-45a3-a25b-a2bd92272971",
  "rat": 1664351431,
  "sid": "715b63c4-0f2a-4fe5-8d28-e830dd361349",
  "sub": "1"
}

Hydra 處理這段資訊的原始碼有一個結構,裡面每個欄位都有對應的原始碼。參考 OpenID Connect Core 1.0 - ID Token 的說明和原始碼所列出來的欄位定義如下:

欄位 必填 說明
at_hash OPTIONAL 3.1.3.6. ID Token 小節有說明,這是從 Access Token 取得部分的資訊雜湊後的結果,這是用來確認 ID Token 與 Access Token 兩個關係的一致性。
aud REQUIRED 此 Token 的接受者。可以是一個字串元素或是 Array 裡有多個字串元素。它的值會是應用程式的唯一識別碼,也就是指授權請求的 client_id
auth_time OPTIONAL 使用者完成身分驗證時間。
exp REQUIRED Token 失效或過期的時間。
iat REQUIRED Token 的發行時間。
iss REQUIRED 發行 ID token 的唯一識別碼,通常會使用網址表示。
jti OPTIONAL RFC 7519 - JSON Web Token (JWT) 4.1.7. "jti" (JWT ID) Claim 裡定義,jti 是 JWT ID 的縮寫,它代表著 JWT 的唯一識別碼。
rat 沒有定義 Request At 的縮寫,指何時發出請求的。
sid 沒有定義 Session ID 的縮寫,再賣一次關子,之後再來說明這個欄位的詳細用法。
sub REQUIRED 使用者的唯一識別碼。

內容所代表的意義知道之後,接著來看該如何驗證這些內容:

  1. iss 必須跟 OP 所提供的 issuer 完全一致。
  2. aud 裡要有包含應用程式所註冊的 client_id,以本例會是 my-rp
  3. exp 代表 Token 過期時間,所以現在時間必須要小於 exp
  4. 如果有 at_hash,則必須確認 Access Token 與 ID Token 的一致性。

以上是簡化過的驗證流程,在 OpenID Connect Core 1.0 - 3.1.3.7. ID Token Validation 小節有定義完整的驗證流程。

ID Token 完成驗證後,它的資訊就是有效且合法可信任的,接著就能保存在應用程式裡,然後在未來可以在其他流程中使用。而在使用前,也可以再次確認 exp 是否有效,來確認是否要重新執行身分驗證。


上一篇
應用程式處理身分驗證回應
下一篇
如何取得 Access Token 的資訊
系列文
30 天與九頭蛇先生做好朋友23
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言