使用令牌交換

使用 Keycloak 設定和使用令牌交換

令牌交換為預覽功能,並未完全支援。此功能預設為停用。

若要啟用,請使用 --features=preview--features=token-exchange 啟動伺服器

令牌交換為技術預覽功能,並未完全支援。

若要使用超過內部令牌到內部令牌交換流程的功能,也請啟用 admin-fine-grained-authz 功能。如需詳細資訊,請參閱啟用和停用功能指南。

令牌交換的運作方式

在 Keycloak 中,令牌交換是使用一組憑證或令牌來取得完全不同的令牌的過程。用戶端可能想要在信任度較低的應用程式上調用,因此可能想要降級它目前擁有的令牌。用戶端可能想要將 Keycloak 令牌交換為為連結的社群提供者帳戶儲存的令牌。您可能想要信任由其他 Keycloak 領域或外部 IDP 發行的外部令牌。用戶端可能需要模擬使用者。以下簡要總結 Keycloak 目前關於令牌交換的功能。

  • 用戶端可以將為特定用戶端建立的現有 Keycloak 令牌交換為針對不同用戶端的新令牌

  • 用戶端可以將現有的 Keycloak 令牌交換為外部令牌,例如連結的 Facebook 帳戶

  • 用戶端可以將外部令牌交換為 Keycloak 令牌。

  • 用戶端可以模擬使用者

Keycloak 中的令牌交換是 IETF 的OAuth 令牌交換規格的非常鬆散的實作。我們稍微擴展了它,忽略了其中一些部分,並鬆散地解釋了規格的其他部分。它是領域的 OpenID Connect 令牌端點上的一個簡單授權類型調用。

/realms/{realm}/protocol/openid-connect/token

它接受表單參數 (application/x-www-form-urlencoded) 作為輸入,輸出取決於您請求交換的令牌類型。令牌交換是一個用戶端端點,因此請求必須提供調用用戶端的驗證資訊。公開用戶端將其用戶端識別碼指定為表單參數。機密用戶端也可以使用表單參數來傳遞其用戶端 ID 和密碼、基本驗證,或管理員在您的領域中配置用戶端驗證流程的方式。

表單參數

client_id

可能需要。使用表單參數進行驗證的用戶端必須提供此參數。如果您使用的是基本驗證、用戶端 JWT 令牌或用戶端憑證驗證,則請勿指定此參數。

client_secret

可能需要。使用表單參數進行驗證並使用用戶端密碼作為憑證的用戶端必須提供此參數。如果您的領域中的用戶端調用是透過不同的方式進行驗證,則請勿指定此參數。

grant_type

必須。參數的值必須為 urn:ietf:params:oauth:grant-type:token-exchange

subject_token

可選。代表正在代表其提出請求的當事方身分的安全令牌。如果您要將現有的令牌交換為新的令牌,則必須提供此參數。

subject_issuer

可選。識別 subject_token 的發行者。如果令牌來自目前的領域,或者可以從 subject_token_type 確定發行者,則可以將其留空。否則,必須指定。有效值是為您的領域配置的 身分提供者 的別名。或是特定 身分提供者 配置的發行者宣告識別碼。

subject_token_type

可選。此參數是透過 subject_token 參數傳遞的令牌類型。如果 subject_token 來自領域且是存取令牌,則此參數預設為 urn:ietf:params:oauth:token-type:access_token。如果是外部令牌,則此參數可能必須指定,也可能不必指定,具體取決於 subject_issuer 的需求。

requested_token_type

可選。此參數代表用戶端想要交換的令牌類型。目前僅支援 oauth 和 OpenID Connect 令牌類型。此預設值取決於是否為 urn:ietf:params:oauth:token-type:refresh_token,在這種情況下,您將在回應中收到存取令牌和重新整理令牌。其他適當的值為 urn:ietf:params:oauth:token-type:access_tokenurn:ietf:params:oauth:token-type:id_token

audience

可選。此參數指定您要為其發行新令牌的目標用戶端。

requested_issuer

可選。此參數指定用戶端想要由外部提供者發行的令牌。它必須是領域內配置的 身分提供者 的別名。

requested_subject

可選。如果您的用戶端想要模擬不同的使用者,則此參數會指定使用者名稱或使用者 ID。

scope

可選。此參數代表用戶端正在請求的 OAuth 和 OpenID Connect 範圍的目標集合。傳回的範圍是範圍參數和存取令牌範圍的笛卡爾乘積。

我們目前僅支援 OpenID Connect 和 OAuth 交換。未來可能會根據用戶需求新增對基於 SAML 的用戶端和身分提供者的支援。

令牌交換請求的回應

交換調用的成功回應將傳回 HTTP 200 回應代碼,其內容類型取決於用戶端要求的 requested-token-typerequested_issuer。OAuth 要求的令牌類型將傳回 JSON 文件,如OAuth 令牌交換規格中所述。

{
   "access_token" : ".....",
   "refresh_token" : ".....",
   "expires_in" : "...."
 }

請求重新整理令牌的用戶端將在回應中收到存取令牌和重新整理令牌。僅請求存取令牌類型的用戶端將在回應中僅收到存取令牌。對於透過 requested_issuer 參數請求外部發行者的用戶端,可能包含也可能不包含到期資訊。

錯誤回應通常屬於 400 HTTP 回應代碼類別,但可能會根據錯誤的嚴重性傳回其他錯誤狀態碼。錯誤回應可能包含內容,具體取決於 requested_issuer。基於 OAuth 的交換可能會傳回 JSON 文件,如下所示

{
   "error" : "...."
   "error_description" : "...."
}

可能會根據交換類型傳回其他錯誤宣告。例如,如果使用者沒有身分提供者的連結,OAuth 身分提供者可能會包含額外的 account-link-url 宣告。此連結可用於用戶端啟動的連結請求。

令牌交換設定需要精細的管理權限知識(如需詳細資訊,請參閱伺服器管理指南)。您需要授予用戶端交換的權限。本章稍後將對此進行更詳細的討論。

本章的其餘部分將討論設定要求,並提供不同交換案例的範例。為簡單起見,我們將目前領域發行的令牌稱為內部令牌,將外部領域或身分提供者發行的令牌稱為外部令牌。

內部令牌到內部令牌交換

透過內部令牌到令牌交換,您有一個發行給特定用戶端的現有令牌,並且您想要將此令牌交換為一個為不同目標用戶端發行的新令牌。您為什麼要這麼做?這通常發生在用戶端擁有為自己發行的令牌,並且需要向其他應用程式發出額外請求,這些應用程式需要在存取令牌內使用不同的宣告和權限時。可能需要此類交換的其他原因包括,如果您需要執行「權限降級」,其中您的應用程式需要在信任度較低的應用程式上調用,並且您不想傳播您目前的存取令牌。

授予交換權限

想要為不同用戶端交換令牌的用戶端需要在管理主控台中授權。您需要在您想要交換的目標用戶端中定義 token-exchange 精細權限。

Target Client Permission
圖 1. 目標用戶端權限
步驟
  1. 已啟用權限切換為開啟

    Target Client Exchange Permission Set
    圖 2. 目標用戶端權限

    該頁面會顯示 token-exchange 連結。

  2. 按一下該連結以開始定義權限。

    會顯示此設定頁面。

    Target Client Exchange Permission Setup
    圖 3. 目標用戶端交換權限設定
  3. 按一下螢幕頂部的麵包屑中的 用戶端詳細資訊

  4. 為此權限定義一個原則。

  5. 按一下螢幕頂部的麵包屑中的 授權

  6. 為此權限定義一個原則。

  7. 按一下 原則 索引標籤。

  8. 按一下 建立原則 按鈕,建立用戶端原則。

    Client Policy Creation
    圖 4. 用戶端原則建立
  9. 輸入作為請求令牌交換的已驗證用戶端的起始用戶端。

  10. 建立此原則後,返回目標用戶端的 token-exchange 權限,並新增您剛定義的用戶端原則。

    Apply Client Policy
    圖 5. 套用用戶端原則

您的用戶端現在有權限調用。如果您未正確執行此操作,如果您嘗試進行交換,將會收到 403 禁止回應。

發出請求

當您的用戶端將現有令牌交換為目標為另一個用戶端的令牌時,您可以使用 audience 參數。此參數必須是您在管理主控台中配置的目標用戶端的用戶端識別碼。

curl -X POST \
    -d "client_id=starting-client" \
    -d "client_secret=the client secret" \
    --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
    -d "subject_token=...." \
    --data-urlencode "requested_token_type=urn:ietf:params:oauth:token-type:refresh_token" \
    -d "audience=target-client" \
    https://127.0.0.1:8080/realms/myrealm/protocol/openid-connect/token

subject_token 參數必須是目標領域的存取令牌。如果您的 requested_token_type 參數是重新整理令牌類型,則回應將包含存取令牌、重新整理令牌和到期時間。以下是您從此呼叫收到的 JSON 回應範例。

當未設定 audience 參數時,參數的值預設為發出令牌交換請求的用戶端。

與機密用戶端不同,不允許公開用戶端使用來自其他用戶端的令牌執行令牌交換。如果您正在傳遞 subject_token,則發行該令牌的(機密)用戶端應符合發出請求的用戶端,或者,如果發行給不同的用戶端,則發出請求的用戶端應屬於設定給令牌的目標對象。

如果您明確設定目標 audience(具有與發出請求的用戶端不同的用戶端),您也應確保為設定為 audience 參數的用戶端配置 token-exchange 範圍權限,以允許發出請求的用戶端成功完成交換。

{
   "access_token" : "....",
   "refresh_token" : "....",
   "expires_in" : 3600
}

內部令牌到外部令牌交換

您可以將 realm token 交換為由外部身分提供者 (Identity Provider) 鑄造的外部 token。此外部身分提供者必須在管理控制台的「身分提供者」區段中設定。目前僅支援基於 OAuth/OpenID Connect 的外部身分提供者,這包括所有社群提供者。Keycloak 不會執行對外部提供者的後端通道交換。因此,如果帳戶未連結,您將無法取得外部 token。要能夠取得外部 token,必須符合以下條件之一:

  • 使用者必須至少使用外部身分提供者登入過一次。

  • 使用者必須透過使用者帳戶服務與外部身分提供者連結。

  • 使用者帳戶已透過使用客戶端啟動的帳戶連結 API 的外部身分提供者連結。

最後,外部身分提供者必須設定為儲存 token,或者,上述其中一個動作必須與您正在交換的內部 token 在同一個使用者會話中執行。

如果帳戶未連結,交換回應將包含一個可用於建立連結的連結。這會在「提出請求」章節中進一步討論。

授予交換權限

在您授權呼叫用戶端與外部身分提供者交換 token 的權限之前,從內部到外部的 token 交換請求將被拒絕,並返回 403 Forbidden 回應。要授予用戶端權限,請前往身分提供者的設定頁面中的「權限」標籤。

Identity Provider Exchange Permission
圖 6. 身分提供者權限
步驟
  1. 已啟用權限切換為開啟

    Identity Provider Exchange Permission Set
    圖 7. 身分提供者權限

    頁面會顯示 token-exchange 連結。

  2. 點擊連結以開始定義權限。

    將出現此設定頁面。

    Identity Provider Exchange Permission Setup
    圖 8. 身分提供者交換權限設定
  3. 按一下螢幕頂部的麵包屑中的 用戶端詳細資訊

  4. 點擊「政策」標籤以建立用戶端政策。

    Client Policy Creation
    圖 9. 用戶端政策建立
  5. 輸入起始用戶端,即請求 token 交換的已驗證用戶端。

  6. 返回身分提供者的 token-exchange 權限,並新增您剛定義的用戶端政策。

    Apply Client Policy
    圖 10. 套用用戶端政策

您的用戶端現在有權限調用。如果您未正確執行此操作,如果您嘗試進行交換,將會收到 403 禁止回應。

發出請求

當您的用戶端將現有的內部 token 交換為外部 token 時,您需要提供 requested_issuer 參數。該參數必須是已設定身分提供者的別名。

curl -X POST \
    -d "client_id=starting-client" \
    -d "client_secret=the client secret" \
    --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
    -d "subject_token=...." \
    --data-urlencode "requested_token_type=urn:ietf:params:oauth:token-type:access_token" \
    -d "requested_issuer=google" \
    https://127.0.0.1:8080/realms/myrealm/protocol/openid-connect/token

subject_token 參數必須是目標 realm 的存取 token。requested_token_type 參數必須為 urn:ietf:params:oauth:token-type:access_token 或留空。目前不支援其他請求的 token 類型。以下是您從此呼叫收到的成功 JSON 回應範例。

{
   "access_token" : "....",
   "expires_in" : 3600
   "account-link-url" : "https://...."
}

如果外部身分提供者由於任何原因未連結,您將收到 HTTP 400 回應碼以及此 JSON 文件

{
   "error" : "....",
   "error_description" : "..."
   "account-link-url" : "https://...."
}

error 宣告將為 token_expirednot_linkedaccount-link-url 宣告提供給用戶端執行客戶端啟動的帳戶連結。大多數(如果不是全部)提供者都需要透過瀏覽器 OAuth 協定進行連結。使用 account-link-url 時,只需將 redirect_uri 查詢參數新增至其中,即可將瀏覽器轉發以執行連結。

外部 token 至內部 token 交換

您可以信任並將外部身分提供者鑄造的外部 token 交換為內部 token。這可用於橋接 realm,或者只是信任來自您的社群提供者的 token。它的運作方式與身分提供者瀏覽器登入類似,如果您的 realm 中不存在新使用者,則會將其匯入。

目前外部 token 交換的限制是,如果外部 token 對應到現有使用者,則除非現有使用者已與外部身分提供者建立帳戶連結,否則不允許交換。

交換完成後,將在 realm 中建立使用者會話,並且您將收到存取 token 和/或重新整理 token,具體取決於 requested_token_type 參數值。您應該注意,這個新的使用者會話將保持活動狀態,直到逾時或直到您呼叫 realm 的登出端點並傳遞這個新的存取 token。

這些類型的變更需要在管理控制台中設定身分提供者。

目前不支援 SAML 身分提供者。Twitter token 也無法交換。

授予交換權限

在執行外部 token 交換之前,您需要授權呼叫用戶端進行交換。此權限的授予方式與內部到外部權限的授予方式相同

如果您還提供一個 audience 參數,其值指向與呼叫用戶端不同的用戶端,則您還必須授予呼叫用戶端交換到 audience 參數中指定的目標用戶端的權限。如何在本章節稍早討論

發出請求

subject_token_type 必須是 urn:ietf:params:oauth:token-type:access_tokenurn:ietf:params:oauth:token-type:jwt。如果類型為 urn:ietf:params:oauth:token-type:access_token,則您需要指定 subject_issuer 參數,並且該參數必須是已設定的身分提供者的別名。如果類型為 urn:ietf:params:oauth:token-type:jwt,則將透過 JWT 中的 iss (issuer) 宣告來匹配提供者,該宣告必須是提供者的別名,或是提供者設定中註冊的簽發者。

對於驗證,如果 token 是存取 token,則將會呼叫提供者的使用者資訊服務來驗證 token。成功的呼叫表示存取 token 有效。如果主體 token 是 JWT,並且如果提供者已啟用簽章驗證,則將會嘗試進行驗證,否則,它將預設為也呼叫使用者資訊服務來驗證 token。

預設情況下,鑄造的內部 token 將使用呼叫用戶端來確定 token 中的內容,並使用為呼叫用戶端定義的協定映射器。或者,您可以使用 audience 參數指定不同的目標用戶端。

curl -X POST \
    -d "client_id=starting-client" \
    -d "client_secret=the client secret" \
    --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
    -d "subject_token=...." \
    -d "subject_issuer=myOidcProvider" \
    --data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \
    -d "audience=target-client" \
    https://127.0.0.1:8080/realms/myrealm/protocol/openid-connect/token

如果您的 requested_token_type 參數是重新整理 token 類型,則回應將包含存取 token、重新整理 token 和到期時間。以下是您從此呼叫收到的 JSON 回應範例。

{
   "access_token" : "....",
   "refresh_token" : "....",
   "expires_in" : 3600
}

模擬

對於內部和外部 token 交換,用戶端可以代表使用者請求模擬其他使用者。例如,您可能有一個管理應用程式需要模擬使用者,以便支援工程師可以偵錯問題。

授予交換權限

主體 token 代表的使用者必須具有模擬其他使用者的權限。請參閱伺服器管理指南,瞭解如何啟用此權限。可以透過角色或細粒度的管理權限來完成。

發出請求

按照其他章節中的描述提出請求,但額外指定 requested_subject 參數。此參數的值必須是使用者名稱或使用者 ID。

curl -X POST \
    -d "client_id=starting-client" \
    -d "client_secret=the client secret" \
    --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
    -d "subject_token=...." \
    --data-urlencode "requested_token_type=urn:ietf:params:oauth:token-type:access_token" \
    -d "audience=target-client" \
    -d "requested_subject=wburke" \
    https://127.0.0.1:8080/realms/myrealm/protocol/openid-connect/token

直接裸模擬

您可以提出內部 token 交換請求,而無需提供 subject_token。這稱為直接裸模擬,因為它對用戶端施加了很大的信任,因為該用戶端可以模擬 realm 中的任何使用者。您可能需要這樣做來橋接無法取得要交換的主體 token 的應用程式。例如,您可能正在整合一個直接使用 LDAP 執行登入的舊版應用程式。在這種情況下,舊版應用程式能夠自行驗證使用者,但無法取得 token。

為用戶端啟用直接裸模擬是非常危險的。如果用戶端的憑證被盜,該用戶端可以模擬系統中的任何使用者。

授予交換權限

如果提供了 audience 參數,則呼叫用戶端必須具有交換到該用戶端的權限。本章節稍早討論了如何設定此功能。

此外,必須授予呼叫用戶端模擬使用者的權限。

步驟
  1. 點擊選單中的「使用者」。

  2. 點擊「權限」標籤。

    User Permissions
    圖 11. 使用者權限
  3. 已啟用權限切換為開啟

    Users Impersonation Permission Set
    圖 12. 身分提供者權限

    頁面會顯示 impersonate 連結。

  4. 按一下該連結以開始定義權限。

    會顯示此設定頁面。

    Users Impersonation Permission Setup
    圖 13. 使用者模擬權限設定
  5. 按一下螢幕頂部的麵包屑中的 用戶端詳細資訊

  6. 為此權限定義一個原則。

  7. 前往「政策」標籤並建立用戶端政策。

    Client Policy Creation
    圖 14. 用戶端政策建立
  8. 輸入起始用戶端,即請求 token 交換的已驗證用戶端。

  9. 返回使用者的「模擬」權限並新增您剛定義的用戶端政策。

    Apply Client Policy
    圖 15. 套用用戶端政策

您的用戶端現在具有模擬使用者的權限。如果您沒有正確執行此操作,如果您嘗試執行此類型的交換,您將收到 403 Forbidden 回應。

不允許公開用戶端進行直接裸模擬。

發出請求

要提出請求,只需指定 requested_subject 參數。這必須是有效使用者的使用者名稱或使用者 ID。如果您願意,也可以指定 audience 參數。

curl -X POST \
    -d "client_id=starting-client" \
    -d "client_secret=the client secret" \
    --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
    -d "requested_subject=wburke" \
    https://127.0.0.1:8080/realms/myrealm/protocol/openid-connect/token

使用服務帳戶擴展權限模型

在授予用戶端交換權限時,您不一定需要為每個用戶端手動啟用這些權限。如果用戶端具有與其關聯的服務帳戶,則可以使用角色將權限分組在一起,並透過將角色分配給用戶端的服務帳戶來分配交換權限。例如,您可以定義一個 naked-exchange 角色,並且任何具有該角色的服務帳戶都可以執行裸交換。

交換漏洞

當您開始允許 token 交換時,您需要注意並謹慎處理各種事情。

首先是公開用戶端。公開用戶端不需要或不要求用戶端憑證即可執行交換。任何擁有有效 token 的人都將能夠模擬公開用戶端,並執行允許公開用戶端執行的交換。如果您的 realm 中有任何不受信任的用戶端,公開用戶端可能會在您的權限模型中打開漏洞。這就是為什麼直接裸交換不允許公開用戶端,並且如果呼叫用戶端是公開的,則會中止並出現錯誤。

可以將 Facebook、Google 等提供的社群 token 交換為 realm token。請小心並警惕允許交換 token 執行的操作,因為在這些社群網站上建立虛假帳戶並不難。使用預設角色、群組和身分提供者映射器來控制分配給外部社群使用者的屬性和角色。

直接裸交換非常危險。您將很大的信任放在呼叫端客戶端上,相信它永遠不會洩漏其客戶端憑證。如果這些憑證洩漏,竊賊就可以在您的系統中冒充任何人。這與擁有現有令牌的機密客戶端形成直接對比。您有兩個驗證因素,訪問令牌和客戶端憑證,而且您只處理一個用戶。因此,請謹慎使用直接裸交換。

本頁內容