好的,我遇到了这个问题,最大的问题之一是它阻止了我的应用程序(target-client)执行刷新令牌工作流。如果我使用预期的目标客户端执行 refresh_token 授权,我会收到一条错误消息 Session doesn't have the required grant. 如果我使用启动客户端(与令牌中的 azp 字段匹配),我会收到 Invalid refresh token. Token client and authorized client don't match。
我发现了这个issue,它把我带到了this,当要求进行令牌交换时,它又指定了starting-client 的client_id 是导致错误azp 访问和刷新令牌和混乱的原因一切都好。我不知道这是不是故意的,文档是错误的,或者这是一个错误,我将来会遇到问题。我会在以后查看这些错误报告以了解更多详细信息。
所以这是进行令牌交换的工作方式(我使用的是 Keycloak 15.0.2),然后从中刷新。这是 Kotlin,但你明白了。这假设您已经执行了必要的配置here。
// tokenManager makes sure I have a valid access token from "starting-client"
// via client_credentials grant. You can swap with however you want to get this token.
val adminToken = keycloak.tokenManager().accessTokenString
// exchange your admin token for starting-client for a user token at target-client
val response = Unirest.post("${keycloakProperties.authServerUrl}/realms/${keycloakProperties.realm}/protocol/openid-connect/token")
.field("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange") // using the keycloak custom token-exchange grant
.field("client_id", "target-client") // set to the target client ID, not the starting as the docs say!
.field("subject_token", adminToken) // seems the exchange grant reads the starting-client from the subject token, so setting "client_id" to "starting-client" (which is what I had been doing) forces the exchanged token into an invalid state
.field("requested_token_type", "urn:ietf:params:oauth:token-type:refresh_token") // refresh_token will issue you both an access and refresh token
.field("audience", "target-client")
.field("requested_subject", userId) // the keycloak user ID we are requesting a token for
.asObject(AccessTokenResponse::class.java) // this is just some DTO so I can deserialize the JSON into an object for easy use, I happened to have this from the Keycloak admin client laying around
.body
// we have our access token now. let's refresh it for the sake of the example, but normally your app would do this periodically on the client end.
val refreshedToken = Unirest.post("${keycloakProperties.authServerUrl}/realms/${keycloakProperties.realm}/protocol/openid-connect/token")
.field("grant_type", "refresh_token")
.field("client_id", "target-client")
.field("refresh_token", accessToken.refreshToken)
.asObject(AccessTokenResponse::class.java)
.body
// voila, refreshedToken has given you a new access and refresh token
log.debug("New access token {}, refresh token {}", refreshedToken.accessToken, refreshedToken.refreshToken)