使用令牌交换

配置和使用 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" \
    http://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
}

内部令牌到外部令牌交换

您可以将领域令牌交换为由外部身份提供商颁发的外部令牌。此外部身份提供商必须在管理控制台的 身份提供商 部分中配置。目前仅支持基于 OAuth/OpenID Connect 的外部身份提供商,包括所有社交提供商。Keycloak 不会对外部提供商执行后台交换。因此,如果帐户未链接,您将无法获取外部令牌。要能够获取外部令牌,必须满足以下条件之一

  • 用户必须至少使用过外部身份提供商登录一次

  • 用户必须通过用户帐户服务与外部身份提供商链接

  • 用户帐户已通过使用 客户端发起的帐户链接 API 的外部身份提供商进行链接。

最后,外部身份提供商必须已配置为存储令牌,或者,必须使用与您要交换的内部令牌相同的用户会话执行上述操作之一。

如果帐户未链接,交换响应将包含一个链接,您可以使用该链接建立链接。这将在 发出请求 部分中进一步讨论。

授予交换权限

在您授予调用客户端与外部身份提供商交换令牌的权限之前,将拒绝内部到外部令牌交换请求,并返回 403 禁止响应。要授予客户端权限,您需要转到身份提供商的配置页面,然后转到 权限 选项卡。

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. 输入起始客户端,即请求令牌交换的已认证客户端。

  6. 返回到身份提供者的token-exchange权限,并添加您刚刚定义的客户端策略。

    Apply Client Policy
    图 10. 应用客户端策略

您的客户端现在拥有调用权限。如果您没有正确执行此操作,在尝试进行交换时会收到 403 禁止响应。

发出请求

当您的客户端将现有的内部令牌交换为外部令牌时,您需要提供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" \
    http://127.0.0.1:8080/realms/myrealm/protocol/openid-connect/token

subject_token参数必须是目标领域的访问令牌。requested_token_type参数必须是urn:ietf:params:oauth:token-type:access_token或留空。目前不支持其他请求的令牌类型。以下是一个从该调用中获得的成功 JSON 响应示例。

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

如果出于任何原因外部身份提供者未链接,您将收到一个包含此 JSON 文档的 HTTP 400 响应代码。

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

error声明将是token_expirednot_linked。提供account-link-url声明,以便客户端执行客户端启动帐户链接。大多数(如果不是全部)提供商要求通过浏览器 OAuth 协议进行链接。使用account-link-url只需向其添加redirect_uri查询参数,您就可以将浏览器转发到执行链接的操作。

外部令牌到内部令牌交换

您可以信任并交换由外部身份提供者颁发的外部令牌,以获取内部令牌。这可用于在领域之间建立桥梁,或仅信任社交提供者的令牌。它的工作原理类似于身份提供者浏览器登录,如果不存在,则会将新用户导入您的领域。

外部令牌交换的当前限制是,如果外部令牌映射到现有用户,则不允许进行交换,除非现有用户已与外部身份提供者建立帐户链接。

交换完成后,将在领域内创建一个用户会话,您将收到访问令牌和/或刷新令牌,具体取决于requested_token_type参数的值。请注意,此新用户会话将保持活动状态,直到超时或您调用领域的注销端点并传递此新访问令牌。

这些类型的更改需要在管理控制台中配置身份提供者。

目前不支持 SAML 身份提供者。Twitter 令牌也不能交换。

授予交换权限

在执行外部令牌交换之前,您需要授予调用客户端进行交换的权限。此权限的授予方式与授予内部到外部权限的方式相同。

如果您还提供了一个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(颁发者)声明匹配提供者,该声明必须是提供者的别名,或提供者配置中已注册的颁发者。

为了进行验证,如果令牌是访问令牌,则将调用提供者的用户信息服务来验证令牌。成功调用意味着访问令牌有效。如果主题令牌是 JWT,并且提供者已启用签名验证,则将尝试进行验证,否则,它将默认为也调用用户信息服务来验证令牌。

默认情况下,生成的内部令牌将使用调用客户端来确定令牌中包含的内容,使用为调用客户端定义的协议映射器。或者,您可以使用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" \
    http://127.0.0.1:8080/realms/myrealm/protocol/openid-connect/token

如果您的requested_token_type参数是刷新令牌类型,则响应将包含访问令牌、刷新令牌和过期时间。以下是一个从该调用中获得的成功 JSON 响应示例。

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

模拟

对于内部和外部令牌交换,客户端可以代表用户请求模拟不同的用户。例如,您可能有一个管理应用程序需要模拟用户,以便支持工程师可以调试问题。

授予交换权限

主题令牌代表的用户必须有权模拟其他用户。请参阅服务器管理指南了解如何启用此权限。可以通过角色或通过细粒度管理权限来完成。

发出请求

按照其他章节中所述进行请求,但另外还需要指定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" \
    http://127.0.0.1:8080/realms/myrealm/protocol/openid-connect/token

直接裸模拟

您可以在不提供subject_token的情况下进行内部令牌交换请求。这被称为直接裸模拟,因为它对客户端非常信任,因为该客户端可以模拟领域中的任何用户。您可能需要这样做来为应用程序架起桥梁,在这些应用程序中不可能获得要交换的主题令牌。例如,您可能正在集成一个将直接与 LDAP 进行登录的旧应用程序。在这种情况下,旧应用程序能够自行验证用户,但无法获取令牌。

为客户端启用直接裸模拟非常危险。如果客户端凭据被盗,那么该客户端可以模拟系统中的任何用户。

授予交换权限

如果提供了audience参数,则调用客户端必须有权将令牌交换到该客户端。如何设置此内容已在此章的前面部分中进行了介绍。

此外,必须授予调用客户端模拟用户的权限。

步骤
  1. 单击菜单中的用户

  2. 单击权限选项卡。

    User Permissions
    图 11. 用户权限
  3. 权限已启用 切换到 开启

    Users Impersonation Permission Set
    图 12. 身份提供者权限

    页面显示了模拟链接。

  4. 单击该链接以开始定义权限。

    将显示此设置页面。

    Users Impersonation Permission Setup
    图 13. 用户模拟权限设置
  5. 单击屏幕顶部的面包屑中的 客户端详细信息

  6. 为此权限定义策略。

  7. 转到策略选项卡并创建客户端策略。

    Client Policy Creation
    图 14. 客户端策略创建
  8. 输入起始客户端,即请求令牌交换的已认证客户端。

  9. 返回到用户的模拟权限,并添加您刚刚定义的客户端策略。

    Apply Client Policy
    图 15. 应用客户端策略

您的客户端现在有权模拟用户。如果您没有正确执行此操作,则尝试进行此类型的交换时,您将收到 403 禁止响应。

不允许公共客户端执行直接裸模拟。

发出请求

要进行请求,只需指定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" \
    http://127.0.0.1:8080/realms/myrealm/protocol/openid-connect/token

使用服务帐户扩展权限模型

授予客户端交换权限时,您并不一定需要为每个客户端手动启用这些权限。如果客户端与其关联了一个服务帐户,则可以使用角色将权限分组在一起,并通过将角色分配到客户端的服务帐户来分配交换权限。例如,您可能定义了一个naked-exchange角色,并且任何具有该角色的服务帐户都可以执行裸交换。

交换漏洞

当您开始允许令牌交换时,您必须注意并小心处理各种问题。

首先是公共客户端。公共客户端没有也不需要客户端凭据来执行交换。任何拥有有效令牌的人都可以模拟公共客户端,并执行公共客户端被允许执行的交换。如果您的领域中存在任何不可信的客户端,则公共客户端可能会在您的权限模型中打开漏洞。这就是为什么直接裸交换不允许公共客户端,并且如果调用客户端是公共客户端,则会中止并显示错误。

可以交换由 Facebook、Google 等提供的社交令牌,以获取领域令牌。请谨慎注意交换令牌被允许执行的操作,因为在这些社交网站上创建假帐户并不难。使用默认角色、组和身份提供者映射器来控制向外部社交用户分配的属性和角色。

直接裸交换非常危险。您对调用客户端非常信任,它永远不会泄露其客户端凭据。如果这些凭据被泄露,则窃取者可以模拟系统中的任何人。这与具有现有令牌的机密客户端形成直接对比。您有两个身份验证因素,访问令牌和客户端凭据,并且您只处理一个用户。因此,请谨慎使用直接裸交换。

在本页上