使用令牌交换

配置和使用 Keycloak 的令牌交换

令牌交换是允许客户端应用程序将一个令牌交换为另一个令牌的过程。在 Keycloak 中,以下两个功能实现了令牌交换

Keycloak 的令牌交换功能如下

  1. 客户端可以交换为特定客户端创建的现有 Keycloak 令牌,以获取同一 realm 中针对不同客户端的新令牌。

  2. 客户端可以将现有的 Keycloak 令牌交换为外部令牌,例如链接的 Facebook 帐户。

  3. 客户端可以将外部令牌交换为 Keycloak 令牌。

  4. 客户端可以模拟用户。

标准令牌交换仅支持用例 (1)。旧版令牌交换支持所有四个用例,但它是一个预览功能。因此,建议使用标准令牌交换 V2,因为它受支持并且将在未来得到维护。旧版令牌交换对于后三个用例很有用,但它可能与未来的 Keycloak 版本不向后兼容。您也可以同时启用这两种令牌交换功能并将它们一起使用。例如,您可以将 V2 提供的内部-内部交换与 V1 支持的其他用例一起使用。有关更多详细信息,请参阅此令牌交换比较

如果您仍然需要旧版令牌交换功能,您还需要启用细粒度管理权限版本 1 (FGAP:v1),因为版本 2 (FGAP:v2) 不支持令牌交换权限。这是故意的,因为令牌交换在概念上并不是真正的“管理”权限,因此没有计划将令牌交换权限添加到 FGAP:v2 中。

标准令牌交换

Keycloak 中的标准令牌交换实现了 令牌交换规范。它允许客户端应用程序交换为特定客户端创建的现有 Keycloak 令牌,以获取颁发给触发令牌交换请求的客户端的新令牌。两个客户端必须在同一 realm 中。

令牌交换流程

考虑以下典型的令牌交换流程

  1. 用户使用 Keycloak SSO 向客户端应用程序 initial-client 进行身份验证。令牌颁发给 initial-client

  2. 客户端 initial-client 可能需要使用 REST 服务 requester-client,这需要身份验证。因此,initial-client 使用令牌将步骤 1 中的访问令牌发送到 requester-client

  3. 为了服务请求,requester-client 可能需要调用另一个服务 target-client。但是,它可能无法使用从 initial-client 发送给它的令牌。例如

    • 令牌的权限或范围不足。

    • target-client 未指定为令牌受众;令牌旨在用于调用 requester-client

    • 令牌具有太多权限;因此,requester-client 可能不想与 target-client 共享它。

      任何这些情况都可能是调用令牌交换的原因。requester-client 可能需要向 Keycloak 服务器发送令牌交换请求,并使用步骤 1 中的原始令牌作为主体令牌,并将其交换为另一个令牌请求令牌

  4. 请求令牌返回给 requester-client。现在可以将此令牌发送到 target-client

  5. target-client 可以满足请求并将响应返回给 requester-client。然后,requester-client 可以继续并将响应返回给步骤 2 中的请求。

令牌交换存在许多其他用例,但前面的示例是最典型的。

令牌交换请求示例

以下是 realm test 中客户端 requester-client 的令牌交换请求示例。请注意,subject_token 是颁发给 initial-client 的访问令牌

POST /realms/test/protocol/openid-connect/token
Authorization: Basic cmVxdWVzdGVyLWNsaWVudDpwYXNzd29yZA==
Content-Type: application/x-www-form-urlencoded
Accept: application/json

grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
subject_token=$SUBJECT_TOKEN&
subject_token_type=urn:ietf:params:oauth:token-type:access_token&
requested_token_type=urn:ietf:params:oauth:token-type:access_token

令牌交换响应示例可能如下所示

{
  "access_token": "eyJhbGciOiJSUzI1NiIsIn...",
  "expires_in": 300,
  "token_type": "Bearer",
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
  "session_state": "287f3c57-32b8-4c0f-8b00-8c7db231d701",
  "scope": "default-scope1",
  "refresh_expires_in": 0,
  "not-before-policy": 0
}

如何启用令牌交换

对于标准令牌交换,默认启用 token-exchange-standard:v2。但是,您还需要为应该发送令牌交换请求的客户端启用标准令牌交换开关,例如先前示例中的 requester-client。请注意,requester-client 必须是保密客户端。此外,与其他授权请求一样,令牌交换请求必须通过为客户端配置的适当客户端身份验证方法进行身份验证。

Enabling Token Exchange
图 1. 启用令牌交换

请求和响应参数

这些参数与 令牌交换规范 一致,描述如下

grant_type

必需。 参数值必须为 urn:ietf:params:oauth:grant-type:token-exchange

subject_token

必需。 代表请求代表其执行的当事方身份的安全令牌。

subject_token_type

必需。 此参数是在 subject_token 参数中传递的令牌类型。当使用标准令牌交换时,这必须是 urn:ietf:params:oauth:token-type:access_token,因为 Keycloak 不支持标准令牌交换的其他类型。

requested_token_type

可选。 此参数表示客户端想要交换的令牌类型。在此版本中,仅支持 oauth 和 OpenID Connect 令牌类型。此参数的默认值为 urn:ietf:params:oauth:token-type:access_token。另一个可能的值是 urn:ietf:params:oauth:token-type:id_token,如果请求颁发给 requester-client 的 ID 令牌。可能的值也可能是 urn:ietf:params:oauth:token-type:refresh_token;在这种情况下,您将在响应中收到访问令牌和刷新令牌。但是,如果启用了标准令牌交换部分中指定的“Standard Token Exchange 中允许刷新令牌”客户端配置选项,则允许刷新令牌。

scope

可选。 此参数表示客户端正在请求的以空格分隔的 OAuth 和 OpenID Connect 范围集。您可以使用 requester-client可选客户端范围。有关更多详细信息,请参阅范围和受众。省略此参数意味着仅有效使用默认客户端范围

audience

可选。 受众指定客户端的 client_id,该客户端应被用作令牌受众。在上面的示例中,它可以是 target-client。允许此参数的多个值,这意味着您希望令牌包含多个受众,以供 requester-client 在多个不同的服务中使用。例如,请求中可以使用 audience=target-client1&audience=target-client2。更多详细信息请参见关于范围和受众的部分

成功的响应以 JSON 格式返回。它包含与其他授权响应类似的参数。以下是一些更值得注意的参数的令牌交换特定信息

access_token

请求的访问令牌。请注意,如果请求指定 requested_token_type=urn:ietf:params:oauth:token-type:id_token,则此参数实际上可能包含 ID 令牌而不是访问令牌。此行为符合 令牌交换规范

refresh_token

刷新令牌。仅当使用 requested_token_type=urn:ietf:params:oauth:token-type:refresh_token 并且客户端已启用从令牌交换颁发刷新令牌时才包含。

issued_token_type

颁发的请求令牌类型。与请求中使用的 requested_token_type 值相同。

token_type

如果颁发的令牌类型是访问令牌或刷新令牌,则通常为 Bearer。如果请求了 ID 令牌,则值为 N_A

范围和受众

令牌交换请求中的 scope 参数与其他授权具有相同的含义。此参数是可选的。当省略时,请求中使用的有效客户端范围是 requester-client默认客户端范围。当使用此参数时,有效客户端范围是默认范围以及可选客户端范围

默认情况下,使用的客户端范围将基于受众文档中指定的已使用的客户端范围和客户端角色,将受众添加到令牌的 aud 声明中。

audience 参数可用于过滤受众,以便 aud 声明仅包含 audience 参数指定的受众。同样,令牌中的客户端角色将被过滤,并且令牌将仅具有 audience 参数指定的客户端的客户端角色。

此外,audience 参数还可用于潜在地过滤客户端范围。它的工作方式类似于用户的客户端范围权限。如果客户端范围不包含任何客户端角色(例如,它包含零个角色或仅包含 realm 角色),则不会对客户端范围进行额外的过滤。但是,如果客户端范围包含任何客户端角色映射,则它必须包含 audience 参数请求的客户端的某些客户端角色。复合角色也包括在考虑范围内。如果客户端范围不包含 audience 请求的客户端的任何客户端角色,则客户端范围将被过滤。

audience 参数可用于过滤来自已使用客户端范围的受众。但是,此参数不会添加更多受众。当省略 audience 参数时,不会进行过滤。因此,audience 参数有效地用于“缩小”令牌的范围,以确保它仅包含请求的受众。但是,scope 参数用于添加可选客户端范围,因此可用于“扩大”范围并添加更多范围。

示例

以下是一些示例,以更好地说明范围和受众的行为。

假设我们有以下 realm

  • 客户端 target-client1,具有客户端角色 target-client1-role

  • 客户端 target-client2,具有客户端角色 target-client2-role

  • 客户端 target-client3,具有客户端角色 target-client3-role

  • 客户端范围 default-scope1。此客户端范围具有客户端角色 target-client1/target-client1-role 的角色范围映射

  • 客户端范围 optional-scope2。此客户端范围具有客户端角色 target-client2/target-client2-role 的角色范围映射

  • 客户端 requester-client,它将客户端范围 default-scope1 添加为默认客户端范围,并将范围 optional-scope2 添加为可选客户端范围

  • 经过身份验证的用户,他是 target-client1-roletarget-client2-role 的成员

以上设置意味着使用范围 default-scope1 会将受众 target-client1 添加到令牌,而使用 optional-scope2 会将受众 target-client2 添加到令牌。这是因为 受众文档 中描述的受众解析。

示例 1

令牌交换请求发送时带有 scope=optional-scope2 且没有受众参数

不会对受众进行过滤。范围和受众将按照 客户端范围受众文档 部分中描述的任何其他授权的情况进行解析。响应令牌将类似于这样(为简洁起见,省略了对此示例不重要的声明)

{
  "azp": "requester-client",
  "scope": "default-scope1 optional-scope2",
  "aud": [ "target-client1", "target-client2" ],
  "resource_access": {
	"target-client1": {
  	  "roles": [ "target-client1-role" ]
	},
	"target-client2": {
  	  "roles": [ "target-client2-role" ]
	}
  },
  ...
}
示例 2

令牌交换请求发送时带有 scope=optional-scope2 且带有 audience=target-client2

与上一个示例类似,但由于包含受众参数,但仅包含 target-client2 客户端,因此 target-client1 受众和客户端角色被过滤。客户端范围 default-scope1 也将被过滤,因为它包含一些客户端角色,但同时,它不包含任何请求受众客户端 target-client2 的客户端角色。因此,令牌将像这样

{
  "azp": "requester-client",
  "scope": "optional-scope2",
  "aud": [ "target-client2" ],
  "resource_access": {
    "target-client2": {
      "roles": [ "target-client2-role" ]
    }
  },
  ...
}
示例 3

令牌交换请求发送时带有 scope=optional-scope2 且带有 audience=target-client2&audience=target-client3

target-client3 不属于令牌受众,因为用户没有任何角色。因此,在这种情况下,请求将被拒绝,因为某些请求的受众不可用。

如令牌交换规范中所述,最佳实践是尽可能缩小令牌范围,并且仅使用所需的受众。理想情况下,使用单个受众。此策略增加了请求被允许的可能性。
如果您有更复杂的部署,其中包含许多不同的范围和受众,则可能难以以适当的方式对其进行建模。考虑使用客户端范围评估选项卡来测试对于给定的用户以及给定的范围和受众集,令牌是否看起来符合预期。

令牌交换 - 附加详细信息

以下附加要点阐明了令牌交换的行为。

  • 公共客户端不支持发送令牌交换请求。V1 对公共客户端有一些非常有限的支持,当公共客户端可以将令牌交换为自身且范围较小时。此用例可以用刷新令牌授权代替。

  • 发送到令牌交换端点的 subject_token 必须将请求客户端设置为 aud 声明中的受众。否则,请求将被拒绝。唯一的例外是客户端交换其自身颁发给它的令牌。交换到自身可能有助于缩小/扩大令牌范围或过滤掉不需要的令牌受众等等。

  • 同意 - 如果请求客户端启用了需要同意,则仅当用户已授予对所有请求范围的同意时,才允许令牌交换

  • 细粒度管理权限 (FGAP) 对于标准令牌交换不是必需的。我们计划最终将 FGAP 集成到未来,但该集成可能适用于所有授权。它不会像令牌交换 V1 中那样仅特定于令牌交换。

  • 可以将令牌交换与客户端策略集成。此集成可能有助于解决某些用例。例如,考虑如果客户端 requester-client 发送带有 scope=some-confidential-scope 的请求的用例,则拒绝令牌交换请求。在此示例中,创建具有 client-scopegrant-typeclient-roles 的组合条件的客户端策略条件可能很有用。

  • 仅当客户端的Standard Token Exchange 中允许刷新令牌开关设置为除 No(默认值)以外的值时,才允许请求刷新令牌。该开关在管理控制台的 OIDC 客户端的高级选项卡中的 OpenID Connect 兼容模式部分中可用。该开关的另一个可用值是 同一会话,这意味着仅当刷新令牌可以使用与主体令牌相同的用户会话时才允许刷新令牌。如果该主体令牌来自临时会话离线会话,则不允许请求刷新令牌。同样,也不允许请求离线令牌(使用 scope=offline_access)。

Enabling refresh token in Token Exchange
图 2. 在令牌交换中启用刷新令牌
  • 令牌交换永远不会创建新的用户会话。如果 requested_token_type 是刷新令牌,它最终可能会在用户会话中为请求客户端创建一个新的客户端会话(如果尚未创建客户端会话)。

  • Keycloak 令牌交换尚不支持 resource 参数。

  • 令牌交换规范提到了模拟和委派的概念。Keycloak 支持模拟用例,但尚不支持委派用例。

撤销

假设有一个颁发给客户端 initial-client 的主体令牌 access-token1,以下是一些与令牌撤销相关的注意事项

  • 对于 access-token1 被交换为客户端 requester-clientaccess-token2 的情况,撤销 access-token1 不会撤销 access-token2。支持访问令牌的“撤销链”意味着相当大的开销。因此,考虑到这一点,管理员必须确保访问令牌是短期的,并在一段时间后自动撤销。

  • 对于 access-token1 被交换为客户端 requester-clientrefresh-token2 的情况,我们尝试支持撤销链。这意味着

    • 撤销 access-token1 也将撤销 refresh-token2。此外,这将从用户会话中删除客户端 requester-client 的客户端会话,因此用户会话中 requester-client 的所有刷新令牌都将被有效撤销

    • 如果 refresh-token2 及其相关的访问令牌被用于进一步令牌交换到不同的客户端,则撤销 access-token1 也将撤销那些后续的令牌交换。换句话说,整个“链”的交换令牌都将被撤销。

    • 请注意,当调用撤销端点时,访问令牌应该是有效的。如果您在原始 access-token1 过期时没有有效的访问令牌,您可以潜在地使用在同一用户会话中颁发给同一客户端的另一个访问令牌。来自“链”的交换令牌(例如 refresh-token2 和其他令牌)应被撤销。

标准令牌交换和旧版令牌交换的比较

虽然前面的章节详细介绍了标准和旧版令牌交换,但以下是对两种令牌交换方法的总体总结比较。

功能 标准令牌交换 V2 旧版令牌交换 V1

内部-内部令牌交换

支持。按照 rfc8693 实现

预览支持。rfc8693 的宽松实现。建议改用 V2

允许的 subject_token_type

仅访问令牌类型

内部-内部为仅访问令牌类型,外部-内部场景为 JWT

允许的 requested_token_type

访问令牌(默认)、刷新令牌、ID 令牌

访问令牌、刷新令牌(默认)、SAML2 断言

scope 参数的行为

与其他授权对齐。Scope 参数表示请求发送令牌交换请求的客户端的可选范围

Scope 参数基于受众参数指定的“目标”客户端的范围。仅支持缩小范围

audience 参数的行为

支持更多值,符合规范。可用于缩小可用受众范围,并仅保留请求的受众。有效缩小每个所需目标受众的令牌范围

支持单个受众值。令牌有效颁发给受众参数请求的客户端,并使用该客户端的范围

公共客户端

不可用。V1 实现的缩小范围可以用刷新令牌授权代替

仅可用于交换客户端自身的令牌。有效缩小范围支持

同意

对于启用了 需要同意 的客户端,只要用户已授予同意,则允许

对于需要同意的客户端不允许

授权

验证请求客户端是否必须在 subject_token 的受众中。与客户端策略集成。没有细粒度管理权限

基于细粒度管理权限版本 1

撤销链

访问令牌不可用。刷新令牌可用

访问令牌和刷新令牌均不可用

符合 rfc8693 的委派

尚不支持

不支持

符合 rfc8693 的 Resource 参数

尚不支持

不支持

联邦令牌交换

尚未实现

作为预览实现

主体模拟(包括直接裸模拟)

尚未实现

作为预览实现

旧版令牌交换

令牌交换是预览版,尚未完全支持。此功能默认禁用。

要启用,请使用 --features=preview--features=token-exchange 启动服务器

令牌交换是 技术预览版,尚未完全支持。

要使用超出 内部令牌到内部令牌交换 流程的功能,还要启用 admin-fine-grained-authz 功能。有关详细信息,请参阅启用和禁用功能 指南。

令牌交换的工作原理

在 Keycloak 中,令牌交换是使用一组凭据或令牌来获取完全不同的令牌的过程。客户端可能想要调用不太受信任的应用程序,因此它可能想要降级其当前拥有的令牌。客户端可能想要将 Keycloak 令牌交换为为链接的社交提供商帐户存储的令牌。您可能想要信任由其他 Keycloak realm 或外部 IDP 颁发的外部令牌。客户端可能需要模拟用户。以下是 Keycloak 当前围绕令牌交换的功能的简短摘要。

  • 客户端可以交换为特定客户端创建的现有 Keycloak 令牌,以获取针对不同客户端的新令牌

  • 客户端可以将现有 Keycloak 令牌交换为外部令牌,即链接的 Facebook 帐户

  • 客户端可以将外部令牌交换为 Keycloak 令牌。

  • 客户端可以模拟用户

Keycloak 中的令牌交换是对 IETF 的 OAuth 令牌交换 规范的非常宽松的实现。我们对其进行了一些扩展,忽略了其中的一些内容,并对规范的其他部分进行了宽松的解释。它是 realm 的 OpenID Connect 令牌端点上的简单授权类型调用。

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

它接受表单参数 (application/x-www-form-urlencoded) 作为输入,输出取决于您请求交换的令牌类型。令牌交换是一个客户端端点,因此请求必须为调用客户端提供身份验证信息。公共客户端将其客户端标识符指定为表单参数。保密客户端也可以使用表单参数来传递其客户端 ID 和密钥、基本身份验证,或者您的管理员在您的 realm 中配置客户端身份验证流程的方式。

表单参数

client_id

可能必需。 对于使用表单参数进行身份验证的客户端,此参数是必需的。如果您使用基本身份验证、客户端 JWT 令牌或客户端证书身份验证,则不要指定此参数。

client_secret

可能必需。对于使用表单参数进行身份验证并使用客户端密钥作为凭据的客户端,此参数是必需的。如果您的 realm 中的客户端调用通过其他方式进行身份验证,则不要指定此参数。

grant_type

必需。 参数值必须为 urn:ietf:params:oauth:grant-type:token-exchange

subject_token

可选。 代表请求代表其执行的当事方身份的安全令牌。如果您要交换现有令牌以获取新令牌,则这是必需的。

subject_issuer

可选。 标识 subject_token 的颁发者。如果令牌来自当前 realm,或者可以从 subject_token_type 确定颁发者,则可以将其留空。否则,必须指定它。有效值是为您的 realm 配置的 身份提供商 的别名。或由特定 身份提供商 配置的颁发者声明标识符。

subject_token_type

可选。 此参数是随 subject_token 参数传递的令牌类型。如果 subject_token 来自 realm 并且是访问令牌,则默认为 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

可选。 此参数指定客户端想要由外部提供商生成的令牌。它必须是 realm 中配置的 身份提供商 的别名。

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 声明。此链接可用于客户端发起的链接请求。

令牌交换设置需要了解细粒度管理权限(有关更多信息,请参阅服务器管理指南)。您需要授予客户端交换的权限。这将在本章后面详细讨论。

本章的其余部分讨论设置要求,并提供不同交换场景的示例。为简单起见,我们将当前 realm 生成的令牌称为内部令牌,将外部 realm 或身份提供商生成的令牌称为外部令牌。

内部令牌到内部令牌交换

对于内部令牌到内部令牌交换,建议使用 标准令牌交换 而不是使用下面描述的旧版令牌交换流程。标准令牌交换是官方支持的。

使用内部令牌到令牌交换,您拥有一个为特定客户端生成的现有令牌,并且您希望将此令牌交换为为不同的目标客户端生成的新令牌。您为什么要这样做?这通常发生在客户端拥有为其自身生成的令牌,并且需要向其他应用程序发出额外的请求,这些应用程序需要在访问令牌中具有不同的声明和权限。可能需要这种类型交换的其他原因是,如果您需要执行“权限降级”,即您的应用程序需要调用不太受信任的应用程序,并且您不想传播您当前的访问令牌。

授予交换权限

想要为不同客户端交换令牌的客户端需要在管理控制台中获得授权。您需要在您想要授权交换的目标客户端中定义 token-exchange 细粒度权限。

Target Client Permission
图 3. 目标客户端权限
步骤
  1. 权限已启用切换为开启

    Target Client Exchange Permission Set
    图 4. 目标客户端权限

    该页面显示一个 token-exchange 链接。

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

    这将显示设置页面。

    Target Client Exchange Permission Setup
    图 5. 目标客户端交换权限设置
  3. 单击屏幕顶部导航栏中的 客户端详情

  4. 为此权限定义策略。

  5. 单击屏幕顶部导航栏中的 授权

  6. 为此权限定义策略。

  7. 单击 策略 选项卡。

  8. 通过单击 创建策略 按钮创建一个 客户端 策略。

    Client Policy Creation
    图 6. 客户端策略创建
  9. 输入作为请求令牌交换的经过身份验证的客户端的起始客户端。

  10. 创建此策略后,返回目标客户端的 token-exchange 权限,并添加您刚刚定义的客户端策略。

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

您的客户端现在有权调用。如果您没有正确执行此操作,如果您尝试进行交换,您将收到 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 参数必须是目标 realm 的访问令牌。如果您的 requested_token_type 参数是刷新令牌类型,则响应将包含访问令牌、刷新令牌和过期时间。以下是您从此调用收到的 JSON 响应示例。

当未设置 audience 参数时,该参数的值默认为发出令牌交换请求的客户端。

与保密客户端不同,不允许公共客户端使用来自其他客户端的令牌执行令牌交换。如果您正在传递 subject_token,则颁发令牌的(保密)客户端应与发出请求的客户端匹配,或者,如果颁发给不同的客户端,则发出请求的客户端应在令牌设置的受众中。

如果您显式设置目标 audience(使用与发出请求的客户端不同的客户端),您还应确保为设置为 audience 参数的客户端配置了 token-exchange 范围权限,以允许发出请求的客户端成功完成交换。

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

内部令牌到外部令牌交换

您可以交换 realm 令牌以获取外部身份提供商铸造的外部令牌。此外部身份提供商必须在管理控制台的“Identity Provider”部分中配置。目前仅支持基于 OAuth/OpenID Connect 的外部身份提供商,这包括所有社交提供商。Keycloak 不会执行到外部提供商的回调交换。因此,如果帐户未链接,您将无法获得外部令牌。要能够获得外部令牌,必须满足以下条件之一

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

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

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

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

如果帐户未链接,则交换响应将包含一个链接,您可以使用该链接来建立链接。这在“发出请求”部分中进行了更详细的讨论。

授予交换权限

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

Identity Provider Exchange Permission
图 8. 身份提供商权限
步骤
  1. 权限已启用切换为开启

    Identity Provider Exchange Permission Set
    图 9. 身份提供商权限

    该页面显示 token-exchange 链接。

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

    将出现此设置页面。

    Identity Provider Exchange Permission Setup
    图 10. 身份提供商交换权限设置
  3. 单击屏幕顶部导航栏中的 客户端详情

  4. 单击 Policies 选项卡以创建客户端策略。

    Client Policy Creation
    图 11. 客户端策略创建
  5. 输入作为已认证客户端的起始客户端,该客户端正在请求令牌交换。

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

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

您的客户端现在有权调用。如果您没有正确执行此操作,如果您尝试进行交换,您将收到 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" \
    https://127.0.0.1:8080/realms/myrealm/protocol/openid-connect/token

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

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

如果外部身份提供商由于任何原因未链接,您将收到 HTTP 400 响应代码以及此 JSON 文档

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

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

外部令牌到内部令牌交换

您可以信任并交换外部身份提供商铸造的外部令牌以获取内部令牌。这可以用于桥接 realm 之间,或者只是信任来自您的社交提供商的令牌。它的工作方式类似于身份提供商浏览器登录,即如果新用户不存在,则会将其导入到您的 realm 中。

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

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

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

目前不支持 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(issuer)声明进行匹配,该声明必须是提供商的别名,或提供商配置中注册的 issuer。

对于验证,如果令牌是访问令牌,将调用提供商的用户信息服务来验证令牌。成功的调用将意味着访问令牌有效。如果 subject 令牌是 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" \
    https://127.0.0.1:8080/realms/myrealm/protocol/openid-connect/token

如果您的 requested_token_type 参数是刷新令牌类型,则响应将同时包含访问令牌、刷新令牌和过期时间。以下是从此调用返回的 JSON 响应示例。

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

模拟

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

此处提到的模拟场景与 令牌交换规范的模拟概念不同。该规范不支持将令牌 subject 模拟为不同的 subject。该规范的语义更意味着“模拟客户端”而不是“模拟用户”。

授予交换权限

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

发出请求

按照其他章节中的描述发出请求,但另外指定 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

直接裸模拟

您可以发出内部令牌交换请求,而无需提供 subject_token。这称为直接裸模拟,因为它对客户端给予了很大的信任,因为该客户端可以模拟 realm 中的任何用户。您可能需要此功能来桥接无法获取 subject 令牌进行交换的应用程序。例如,您可能正在集成一个直接使用 LDAP 执行登录的遗留应用程序。在这种情况下,遗留应用程序能够自行验证用户身份,但无法获取令牌。

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

授予交换权限

如果提供了 audience 参数,则调用客户端必须具有交换到该客户端的权限。本章前面讨论了如何设置此权限。

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

步骤
  1. 单击菜单中的 Users

  2. 单击 Permissions 选项卡。

    User Permissions
    图 13. 用户权限
  3. 权限已启用切换为开启

    Users Impersonation Permission Set
    图 14. 身份提供商权限

    该页面显示 impersonate 链接。

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

    这将显示设置页面。

    Users Impersonation Permission Setup
    图 15. 用户模拟权限设置
  5. 单击屏幕顶部导航栏中的 客户端详情

  6. 为此权限定义策略。

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

    Client Policy Creation
    图 16. 客户端策略创建
  8. 输入作为已认证客户端的起始客户端,该客户端正在请求令牌交换。

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

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

您的客户端现在有权模拟用户。如果您未正确执行此操作,则在尝试进行此类交换时,您将收到 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 角色,任何具有该角色的服务帐户都可以执行裸交换。

交换漏洞

当您开始允许令牌交换时,您必须注意并小心各种事项。

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

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

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

在此页面上