You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Spring Boot+Keycloak中用Token Exchange实现身份模拟?

实现Keycloak Direct Naked Impersonation的Spring Boot OAuth2方案

核心思路

Direct Naked Impersonation的本质是先用Client Credentials获取服务自身的访问令牌,再将该令牌作为subject_token发起Token Exchange请求,换取目标用户的身份令牌。我们可以通过组合Spring OAuth2的原生组件串联这两个流程,无需自定义认证类。

步骤1:配置双Client Registration

在application.yml中配置两个客户端注册:一个用于Client Credentials授权,另一个用于Token Exchange授权(复用同一Keycloak客户端信息):

spring:
  security.oauth2.client:
    registration:
      keycloak-client-creds:
        client-id: '${SPRING_OAUTH_CLIENT_ID:service-s}'
        client-secret: '${SPRING_OAUTH_CLIENT_SECRET}'
        authorization-grant-type: client_credentials
        provider: keycloak
      keycloak-token-exchange:
        client-id: '${SPRING_OAUTH_CLIENT_ID:service-s}'
        client-secret: '${SPRING_OAUTH_CLIENT_SECRET}'
        authorization-grant-type: urn:ietf:params:oauth:grant-type:token-exchange
        provider: keycloak
    provider:
      keycloak:
        issuer-uri: '${SPRING_OAUTH_ISSUER_URI}'

步骤2:编写WebClient配置代码

通过组合Spring OAuth2的授权管理器和Token响应客户端,实现两个流程的串联:

@Bean
fun legacyServerGraphQLClient(
    legacyServerProperties: LegacyServerProperties,
    clientRegistrationRepository: InMemoryReactiveClientRegistrationRepository,
    clientService: ReactiveOAuth2AuthorizedClientService
): WebClientGraphQLClient {
    // 1. 配置Client Credentials授权管理器,用于获取服务自身的令牌
    val clientCredsManager = AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
        clientRegistrationRepository,
        clientService
    ).apply {
        setAuthorizedClientProvider(ClientCredentialsReactiveOAuth2AuthorizedClientProvider())
    }

    // 2. 配置Token Exchange的响应客户端,动态注入subject_token等参数
    val tokenExchangeClient = WebClientReactiveTokenExchangeTokenResponseClient().apply {
        addParametersConverter { grantRequest ->
            // 从授权请求属性中获取动态传入的目标用户和受众(可选,若固定可硬编码)
            val requestedSubject = grantRequest.attributes["requested_subject"] as String
            val audience = grantRequest.attributes["audience"] as String

            // 先获取Client Credentials令牌,作为subject_token
            clientCredsManager.authorize(
                OAuth2AuthorizeRequest.withClientRegistrationId("keycloak-client-creds")
                    .principal(grantRequest.principal)
                    .build()
            ).map { authorizedClient ->
                LinkedMultiValueMap<String, String>().apply {
                    add("audience", audience)
                    add("requested_subject", requestedSubject)
                    add("subject_token", authorizedClient.accessToken.tokenValue)
                    add("subject_token_type", "urn:ietf:params:oauth:token-type:access_token")
                }
            }
        }
    }

    // 3. 配置Token Exchange授权管理器
    val tokenExchangeManager = AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
        clientRegistrationRepository,
        clientService
    ).apply {
        setAuthorizedClientProvider(TokenExchangeReactiveOAuth2AuthorizedClientProvider().apply {
            setAccessTokenResponseClient(tokenExchangeClient)
        })
    }

    // 4. 配置WebClient的OAuth2过滤器
    val oauthFilter = ServerOAuth2AuthorizedClientExchangeFilterFunction(tokenExchangeManager).apply {
        setDefaultClientRegistrationId("keycloak-token-exchange")
    }

    return MonoGraphQLClient.createWithWebClient(WebClient.builder()
        .baseUrl(legacyServerProperties.httpUrl)
        .filter(oauthFilter)
        .build()
    )
}

步骤3:动态传入目标用户和受众

在发起GraphQL请求时,通过attributes传递动态参数(若参数固定可跳过此步,直接在代码中硬编码):

// 示例:从事件中获取目标用户ID
val targetUserId = event.originalUserId

legacyServerGraphQLClient.document(query)
    .attribute(OAuth2AuthorizeRequestAttributes.withAuthorizedClientId("keycloak-token-exchange")
        .attribute("requested_subject", targetUserId)
        .attribute("audience", "legacy-api-A")
        .build())
    .execute()
    .subscribe(...)

关键注意事项

  1. Keycloak客户端权限配置:确保你的服务客户端(service-s)已开启Impersonation权限,并且在Keycloak的客户端设置中允许使用Token Exchange授权类型。
  2. 令牌缓存:Spring的ReactiveOAuth2AuthorizedClientService会自动缓存有效的Client Credentials令牌,避免重复请求Keycloak。
  3. Reactive环境适配:所有流程均基于Reactive API实现,避免使用block()等阻塞操作,确保服务的响应式特性。

内容的提问来源于stack exchange,提问作者Crystark

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 23:13:10