Keycloak 内部令牌交换中的 NPE

NPE in Keycloak internal token-exchange

我试图在 Keycloak 17.0.1 中实现内部令牌交换,但是,服务器 returns 出现未知错误 (NullPointerException)。

我的场景是:我有三个微服务,ABC . A调用B,这是一个中间服务,需要调用服务C。所以,我不想传播原始令牌 (A) 来调用 (C)。相反,我想交换令牌,所以 B 向 Keycloak 发出令牌交换请求以获取 new 令牌,然后调用服务 C.

我做了什么:

  1. 我有一个“原始”客户,他有自己的 client-id/client-secret
  2. 我创建了另一个客户端“目标”并配置了令牌交换策略,假定该策略中的“原始”客户端。

最后是 cURL 调用:

curl -L -X POST 'http://localhost:8080/realms/myrealm/protocol/openid-connect/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=target' \
--data-urlencode 'client_secret=<< TARGET SECRET >>' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode 'subject_token=<< ORIGINAL CLIENT TOKEN >>' \
--data-urlencode 'requested_token_type=urn:ietf:params:oauth:token-type:refresh_token' \
--data-urlencode 'audience=original'

回复:

2022-04-19 16:05:16,154 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (executor-thread-37) Uncaught server error: java.lang.NullPointerException
        at org.keycloak.protocol.oidc.TokenManager.attachAuthenticationSession(TokenManager.java:539)
        at org.keycloak.protocol.oidc.DefaultTokenExchangeProvider.exchangeClientToOIDCClient(DefaultTokenExchangeProvider.java:336)
        at org.keycloak.protocol.oidc.DefaultTokenExchangeProvider.exchangeClientToClient(DefaultTokenExchangeProvider.java:315)
        at org.keycloak.protocol.oidc.DefaultTokenExchangeProvider.tokenExchange(DefaultTokenExchangeProvider.java:233)
        at org.keycloak.protocol.oidc.DefaultTokenExchangeProvider.exchange(DefaultTokenExchangeProvider.java:123)
        at org.keycloak.protocol.oidc.endpoints.TokenEndpoint.tokenExchange(TokenEndpoint.java:789)
        at org.keycloak.protocol.oidc.endpoints.TokenEndpoint.processGrantRequest(TokenEndpoint.java:204)
        at jdk.internal.reflect.GeneratedMethodAccessor344.invoke(Unknown Source)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:170)
        at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:130)
        at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:660)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:524)
        at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget(ResourceMethodInvoker.java:474)
        at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:476)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:434)
        at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:192)
        at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:152)

我是不是漏掉了什么?

更新 我让它工作的唯一方法是通过在客户端 A 的请求中使用“密码”授权类型来“强制”在 Keycloak 中创建一个会话。因此,我创建了一个用户 foo 并在其中获得了一个令牌方式:

POST http://localhost:{{keycloak_port}}/realms/{{keycloak_realm}}/protocol/openid-connect/token 
Authorization: Basic original:12345
Content-Type: application/x-www-form-urlencoded

grant_type=password
&username=foo
&password=bar

这样,为客户端 original 创建了一个会话,target 客户端的令牌交换请求成功了。

不过我想知道这是否是一种正确的方法。

client configuration

因为我正在使用 client_credentials OAuth2.0 流程,所以我必须在 Keycloak 中启用“使用刷新令牌来授予客户端凭据”(clients/settings/OpenID 连接兼容模式),然后切换前面提到的选项。 尽管 OAuth2.0 声明 refresh_tokens 不应在此流程中使用,但我找不到其他解决方案。 有关更多详细信息,请参见附图。