使用訪問令牌保護微服務

zupk8699 7年前發布 | 18K 次閱讀 微服務 OpenID

使用訪問令牌保護微服務

基于微服務的架構正日益普遍,它是 一種獨特的軟件應用程序設計方式,即設計成一系列可獨立部署的服務"。如果朝著這種應用程序構成方式發展,會產生大量的網絡流量。

來自用戶的單個請求可能會導致在一系列服務(合并起來實現應用程序)之間產生許多后續請求。在理想情況下,每個后續請求都應以安全的方式發出:您想知道,由任何給定服務處理的任何給定請求都源自某位已知用戶。換句話說,任何給定請求都應包含在應用程序內的正常預期流量中,而不是在某人注意到恰好可以從他所在位置訪問您的后端服務時,對您的應用程序架構進行濫用。

OpenID Connect 概述

"RP 中的用戶向 OP 發送信息,然后 OP 向 RP 返回信息,RP 又向 OP 返回信息,然后再向 RS 發送信息,RS 回復 RP,然后 RP 再回復用戶。"

明白了嗎?好極了!

如果沒明白,不必擔心:您唯一需要真正明白的就是,OpenID Connect 似乎喜歡使用大量縮寫(它甚至將自己的名稱縮寫為 OIDC )。我們首先將這個來自 OpenID 安全性表述的概述翻譯為人們更容易理解的表達形式(所有這些信息的權威來源實際上是 OpenID Connect 規范,但是,如果對這些規范一無所知,您可能也很難理解)。

從用戶體驗的角度講,您可以使用瀏覽器提供的難看但實用的表單來執行 基本身份驗證 ,以及用戶體驗更好的自定義的登錄表單,帶有 我忘記密碼了 按鈕。從服務器的角度看,這兩種方法都涉及針對某個配置好的數據存儲(數據庫、LDAP 等)來驗證用戶憑證。如果該數據存儲是由不懂安全的人設置的,那么您的存儲庫中就充滿了讓黑客垂涎三尺的用戶憑證。

“ "您有一個裝滿用戶憑證的存儲庫,這些憑證讓黑客垂涎三尺。" ”

人們已形成一種認識:您(通常)并不(真正)想要自行保管這些數據。您真正想要知道的只是:"用戶是誰?"。由于這些用戶中許多已經有電子郵件帳戶或社交媒體帳戶,而且他們經常使用這些帳戶為自己授權,所以(對于所有各方而言)最好允許用戶使用這些憑證登錄您的 Web 應用程序,而不是讓他們(和您)創建另一個需要保管的用戶 ID 和密碼。

這就是 OpenID Connect 發揮用武之地。它為應用程序提供了一種驗證用戶身份的途徑,且無需負責管理這些用戶的身份驗證或授權。

OpenID Connect 需要至少兩個服務:一個充當身份驗證器,另一個依賴于這個身份驗證器。第一個被認為是 OpenID 提供方 (OP),第二個被認為是 依賴方 (RP)。在我們的場景中,該組合中還有第三個服務:用戶將信息發送到的應用程序是 RP,但 RP 會將信息發送到另一個服務,我們稱之為 依賴服務 (RS)。

可以配置 Liberty 來充當 OpenID 提供方(也就是 OP),或者也可以將它配置為依賴方 (RP) 來保護應用程序。但是,它目前沒有提供現成的內置方式來保護依賴方應用程序對其他服務或應用程序 (RS) 的繼續調用。

訪問令牌傳播

OpenID Connect 在概念上具有訪問令牌的意思,可由 OpenID 提供方查詢訪問令牌來檢索經過身份驗證的用戶的信息。此令牌是您在執行 OpenID Connect 身份驗證后最終得到的工件之一。

身份驗證期間的一般事件序列是:

  1. 用戶訪問應用程序 (RP)。
  2. RP 將用戶重定向到 OpenID 提供方 (OP) 以執行身份驗證。
  3. OP 對用戶進行身份驗證。
  4. OP 將用戶和一個 auth_token 重定向回 RP。
  5. RP 然后使用 auth_token 與 OP 換取一個 access_token 和 id_token。
  6. 大功告成!

使用訪問令牌保護微服務

圖字: 用戶 應用程序

依賴方 (RP)/客戶端

OpenId Connect 提供方 (OP)

此序列看起來可能非常復雜,但是,如果您使用 非死book、Google 或 推ter 憑證登錄過網站,那么您已經經歷了這個過程:

  • 首先,您去訪問應用程序 (RP)(我們假設它是一個討論論壇)并單擊 Sign in with 非死book(使用 非死book 帳戶登錄)按鈕。
  • 這會跳轉到一個 非死book 頁面 (OP)。
  • 在這里對您執行身份驗證,而且您可能要同意您希望應用程序對您的 非死book 個人資料信息擁有某種訪問類型。
  • 然后,您返回到討論論壇,使用您的 非死book ID 登錄。流程的最后一部分是,該論壇使用 auth_token 換取 access_token 和 id_token,這部分對您是不可見的。

盡管身份驗證/授權被委托給了第三方,但我們知道,應用程序仍然知道 是誰、用戶 ID、用戶名、句柄……

“ "……身份驗證/授權已委托給第三方,但應用程序仍然知道'您'是誰。" ”

我們假想的論壇應用程序需要代表用戶調用其他微服務,以獲取該用戶的私人消息或建立未讀帖子列表。在這兩種情況下,被調用的其他服務都需要知道用戶的身份,可能還需要知道用戶在執行身份驗證時同意共享的信息類型(即范圍)。

盡管論壇應用程序 (RP) 可能只將用戶 ID 傳遞給每個被調用的其他微服務,但這樣做也非常不安全。目標微服務無法檢查它們收到的用戶 ID 是否與發出初始請求的用戶相符,或者無法知道該用戶是否已登錄。

RP 可以不傳遞用戶 ID,而是在調用其他服務時轉發它與 OP 初始交互期間獲取的 access_token。然后,每個服務實例 (RS) 可以利用 access_token 與 OP 取得聯系,以獲取自己的 id_token 版本,后者會向它們轉告用戶的身份和范圍。有了這些信息,服務就可以調整針對該用戶的響應,無需自行對用戶進行身份驗證。

請注意一般流程:每個服務收到 access_token(可能是 RP 或調用鏈中的一個后續服務),并聯系 OP 以換取 id_token。此方法的一個好處是,每個服務都有機會注意到 access_token 已過期并做出適當的反應。該方法也有一個不足之處,與 OP 的通信可能會成為瓶頸。

必須考慮應用程序(大致)將如何處理令牌過期或令牌在請求"中途"被撤銷所帶來的影響。恰當處理此類情況可能給應用程序復雜性帶來很大影響。

關于示例

我們設計了一個示例應用程序來演示如何傳播訪問令牌。該應用程序被配置了 3 個 Liberty 服務器,每個服務器分別扮演 OpenID 提供方 (OP)、 依賴方 (RP) 和 依賴服務 (RS) 這 3 個角色中的一個。也可以將這些角色組合在單個服務器中,但是,為避免角色混淆,我們將它們分開了。

OpenID 提供方 (OP)

我們創建了一個示例 OpenID 提供方 (OP) 來演示該參與者的行為,而不是根據觀察流入一些常見網站(非死book、Google、推ter,等等)的網絡流量進行演示。

用戶注冊表

OP 的職責是對用戶進行身份驗證,Liberty 提供一種簡單的方法在 server.xml 中定義基本用戶注冊表權利,該方法對我們的目的而言足夠用了。

這個示例所需的服務器配置包含在 'access-token-op-wlpcfg' 項目中:

使用訪問令牌保護微服務

這段代碼定義了一個用戶注冊表,其中只有一個用戶 user 和密碼 password 。它顯然不能用于生產用途!可以通過配置 Liberty 來采用其他身份驗證方法(比如 LDAP),但我們在這里是想重點演示訪問令牌傳播,所以我們堅持采用這種簡單方法。

basicRegistry 配置元素需要存在 appSecurity 特性,所以我們會將此特性添加到配置中的 featureManager 代碼塊中:

使用訪問令牌保護微服務

OAuth 提供方

我們需要將服務器配置為 OAuth 提供方,并配置它允許哪些客戶端連接到 OP 來執行身份驗證。在這個示例中,OP 僅有兩個直接客戶端:RP(我們的 Web 應用程序)和 RS(將與該應用程序進行通信的服務):

使用訪問令牌保護微服務

對于每個客戶端(RP 和 RS),會使用 name 和 secret 來驗證連接到 OP 的客戶端。我們會在這些客戶端各自的服務器配置中再次看到這些名稱和密鑰。在這里,我們將密鑰保留為明文,但是如果您可以選擇的話,可以使用 securityUtility 對它們進行編碼。redirect URL 是身份驗證后的重定向目標:OP 將在用戶通過身份驗證后將客戶端(通常是瀏覽器)重定向到這個地方。RS 不需要重定向 URL,因為它不受 OpenID Connect 保護。

處理 OAuth 請求時,Liberty 負責將 access_token 轉換為 id_token。對于 RP,這很方便,但它對 RS 沒有什么幫助。introspectTokens=true 屬性向 RS 授予特殊權限,使之可以使用內省功能來用 access_token 換取 id_token。

我們不需要顯式地將任何特性添加到 OAuth 配置的 server.xml 中,因為我們將使用 OpenID Connect,它會自動為我們引入所需的 OAuth 功能。

OpenID Connect 提供方

現在我們已經定義了一個用戶注冊表和 OAuth 提供方,我們還需要放入 OpenID Connect 功能區,并將服務器配置為 OpenID 提供方。

使用訪問令牌保護微服務

這里有幾點需要解釋一下:

  • id 屬性將被用作 OpenID 提供方的 URL 的一部分。為了明顯起見,我們直接使用 OP ,您會看到它最終位于 URL 中的何處。
  • 需要使用 keystoreRef,因為我們已選擇使用經過非對稱簽名的 id_tokens。我們還需要聲明,需要使用所引用的密鑰庫中的哪個 keyAliasName 來對令牌進行簽名。
  • oauthProviderRef 是對我們上面定義的 OAuth 元素的引用。

在生產配置中,不可能在 server.xml 中以這種方式聲明客戶端,更可能在數據庫中管理它們。了解有關的更多信息。

沒有為 OP 服務器聲明任何應用程序。充當 OP 的功能全都來自 Liberty 本身提供的特性,包含在 openidConnectServer 特性中。我們還需要使用 appSecurity-2.0 特性來支持基本注冊表,最終,我們得到 OP 的一個完整組合的 featureManager 代碼塊,如下所示:

使用訪問令牌保護微服務

依賴方 (RP)

依賴方 (RP) 是與用戶的第一個接觸點。應用程序本身很簡單,其用途是展示使用傳播的訪問令牌對 RS 進行連鎖調用。

'access-token-rp-wlpcfg' 項目包含 RP 的配置,該配置比 OP 的配置還要簡單:

使用訪問令牌保護微服務

另請注意:

  • id 很重要,它是我們在 OP 中注冊的用于重定向的 URL 的一部分。我們選擇使用的 id 是 "RP",而且如果您返回去看一下 oauthProvider 配置,您會看到:https://127.0.0.1:9401/oidcclient/redirect/RP。
  • clientId 和 clientSecret 必不可少,而且必須與 OP 中的 RP 客戶端定義相符。
  • 我們告訴該客戶端希望使用經過非對稱加密的令牌,而且通過指定 trustStoreRef 和 trustAliasName 來告訴它使用哪個密鑰庫和密鑰來驗證該簽名。
  • 最后,我們為 OP 配置了授權和令牌端點 URL。請注意,OP 在 URL 中(就像我說的那樣)。如果您沒有創建自己的 OpenID 提供方,可以通過剪切和粘貼來獲得這里的值。

與服務器一樣,我們通過向 server.xml 中的 featureManager 代碼塊添加該特性,實現對 openidConnectClient 的支持。我們還使用了 appSecurity(它引入了 SSL)和 servlet 來托管應用程序:

使用訪問令牌保護微服務

現在僅剩下應用程序本身的定義了:

使用訪問令牌保護微服務

您會注意到,這里沒有顯式的綁定來聲明會使用 openID Connect 保護此應用程序。當您在 Liberty 服務器配置中聲明一個 openidConnectClient 后,就可以將它應用于所有應用程序。您可以將一個服務器配置為擁有多個 openidConnectClient,這些客戶端使用 authFilterRefs 來決定哪些應用程序受哪個客戶端定義保護,請參見 知識中心 ,了解有關的更多信息。

RP 應用程序實現

上面配置的 openIdConnectClient 為應用程序處理大量工作。在請求到達應用程序代碼時,用戶已被重定向到 OP,并經過了身份驗證,而且 Liberty 已執行了令牌交換,以從 OP 獲取 id_token 和 access_token。這真是個好東西!但我們編寫的服務需要調用另一個服務。這意味著我們需要更深入地挖掘,以便獲得用于驗證 RP 調用的原始令牌。

所幸這不是很棘手。原始令牌可通過 com.ibm.websphere.security.auth.WSSubject 接口獲取,就像下面的代碼段中一樣:

使用訪問令牌保護微服務

獲得字符串形式的令牌后,現在,我們可以調用 RS 了,我們可以通過 JAX-RS 或其他任何途徑進行調用,只要以參數形式傳遞該訪問令牌即可。沒有標準的令牌傳遞方式,通常的做法是將令牌填充到 HTTP 請求標頭中。決定了采用哪種機制,就應該將其硬編碼為 RS 服務 API 的一部分。

下面的代碼段使用 HttpURLConnection 來調用該請求,并添加 access_token 作為 GET 參數:

使用訪問令牌保護微服務

在實際應用程序中,可能還會對實際結果進行后續處理,包括處理錯誤條件或令牌撤銷/過期。響應可以是從 JSON 到二進制數據的任何形式。在我們的示例中,響應是一個字符串,詳細描述了 RS 在收到請求后執行的處理。

依賴服務 (RS)

這使依賴服務 (RS) 與其同級服務器相比更加簡潔。它擁有常見的 SSL 密鑰庫元素和應用程序聲明(在本例中,甚至連應用程序聲明也是可選的;您可以丟棄它)。

使用訪問令牌保護微服務

為了讓應用程序配置位于 server.xml 中和代碼以外的地方,我們還在服務器配置中定義了客戶端 ID、密鑰和內省 URL。我們稍后會分析應用程序中的這些部分:

使用訪問令牌保護微服務

我們使用 SSL 和 servlet 特性來托管 RS 應用程序。為了省去我們的一些工作,我們還將使用 Liberty JSONP 特性執行 JSON 解析。所有這些組成一個類似下圖的 featureManager 代碼塊:

使用訪問令牌保護微服務

RS 沒有針對 OpenID Connect 的服務器配置,因為身份驗證都是在應用程序代碼自身內處理的。

RS 應用程序實現

該應用程序需要獲得所提供的 access_token,并與 OP 一起檢查它。首先,我們獲得請求方(在我們的示例中,它只是一個請求參數)所提供的 access_token:

使用訪問令牌保護微服務

然后,我們需要在 OP 上使用內省 URL 來獲取 id_token。此代碼段使用 HttpURLConnection 向 OP 發出 POST 請求,并使用該 access_token 作為參數。RS 使用自己的客戶端 ID 和密鑰向 OP 執行驗證,我們已在 OP 的 server xml 中聲明了這些客戶端 ID 和密鑰。有關內省端點的更多信息,請查閱 知識中心 。有關在使用 OpenID Connect 時支持的所有 Liberty 端點 URL 的概述,請參見 知識中心

在這里,我們使用了 JDNI 從 server.xml 中獲取內省 URL、客戶端 ID 和密鑰,我們之前將它們存儲在了這里。server.xml 中的值甚至可以設置為環境變量,以便推送 RS 來執行云風格的部署,無需更改 server.xml:

使用訪問令牌保護微服務

這些已足以傳輸該請求。如果您使用諸如 HttpClient 而不是 HttpURLConnection 這樣的庫,該代碼可能會更簡短一些,但基本原理是相同的。

發送請求后,我們需要讀回令牌響應并處理它,像這樣:

使用訪問令牌保護微服務

對內省請求的響應是一個包含各種字段的 JSON 對象,我們在此階段只對一個字段感興趣:JSON 對象的 active 字段,它將指出 access_token 是否仍然有效。我們在這里使用了 Liberty 中的 jsonp-1.0 特性來解析來自 OP 的 JSON 響應,并獲取 active 字段,以便可以處理它。

最后,我們可以執行常規的 RS 處理(或者不執行):

使用訪問令牌保護微服務

因為這只是一個使用 access_token 傳播身份的示例,我們沒有任何處理要執行,所以我們只將令牌上的一些信息轉儲到輸出中。如果令牌過期,我們會輸出一條表明該情況的消息。

只需少量工作,就可以將此邏輯遷移到 ServletWrapper 中,讓應用程序只關注自己的邏輯。

關于密鑰和密鑰庫的一些說明

此示例同時使用了 SSL 和已簽名的 ID 令牌(包含在 OpenID Connect 流中)。因此,最終我們還需要處理 SSL 證書和密鑰。我們的 3 個服務器使用了不少 SSL 證書和密鑰:

  • OpenID 提供方服務器使用的密鑰庫:
    • OpenID Connect 密鑰庫:包含該提供方用于對返回給依賴方的 id_tokens 執行簽名的私鑰。
    • OpenID 提供方密鑰庫:包含該提供方用于保護其 SSL 通信的私鑰。
    </li>
  • 依賴方服務器使用的密鑰庫:
    • 依賴方密鑰庫:包含依賴方用于保護其 SSL 通信的私鑰。
    • OpenID 提供方信任庫:包含依賴方用于驗證來自 OpenID 提供方的 id_tokens 上的簽名的公鑰。
    • 依賴方信任庫:包含用于依賴服務 SSL 和 OpenID 提供方 SSL 的公鑰。
    • </ul> </li>
    • 依賴服務服務器使用的密鑰庫:
    • 依賴服務密鑰庫:包含用于保護其 SSL 通信的私鑰。
    • 依賴服務信任庫:包含用于 OP SSL 的公鑰,使應用程序能夠與 OP 進行通信。
    • </ul>

      如果使用 Gradle 構建該示例,則會根據需要創建每個密鑰庫并導出/導入公鑰。這可確保創建示例時仍在密鑰的有效期內。在生產環境中,SSL 密鑰庫不可能擁有自簽名證書。OpenID 密鑰庫也應包含由某個受信任的根證書頒發機構簽名的證書。

      Gradle

      擁有 3 個服務器和兩個應用程序,這使得編譯、部署和測試工作變得非常有趣,幸運的是,我們編寫了一些 Gradle 來將它們銜接起來。

      對父文件夾運行 gradle build 將下載 Liberty,安裝所需的特性(我們這里只用到了最少的部分),創建密鑰庫,將代碼編譯到 WAR 應用程序中,并將應用程序部署到服務器。

      需要執行一些設置:

      1. 要生成密鑰庫,必須將 JAVA_HOME 設置為指向我們的 JDF,否則 Gradle 無法找到它需要調用來生成密鑰庫的 keytool 二進制程序。
      2. 為了能夠下載 Liberty,我們需要將許可代碼添加到 gradle.properties 文件中。我們不能默認將許可代碼放到該文件中,因為將許可代碼放在屬性中表明該許可已被讀取和接受。您可以讀取 當前許可 并尋找 D/N: 行來獲取許可代碼。

      完成上述操作后,輸入下面的命令:

      gradle build publishToMavenLocal

      這會編譯所有代碼并部署應用程序。然后,如果您希望測試應用程序,可輸入:

      gradle access-token:start

      這會啟動形成 access-token 示例的 3 個服務器(OP、RP 和 RS)。類似地,要關閉這些服務器:

      gradle access-token:stop

      在服務器運行時,您可以訪問:

      https://127.0.0.1:9401/access-token-rp-application

      這會啟動 通過 OP 執行身份驗證 的完整過程。使用我們之前在 OP server.xml 中配置的用戶 ID user 和密碼 password 進行登錄。

      英文鏈接:

       

      來自:http://www.ibm.com/developerworks/cn/cloud/library/cl-using-access-tokens-secure-microservices/index.html?ca=drs-

       

 本文由用戶 zupk8699 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!