If you want to get a bit more context regarding this question, know this is a follow-up question to: How can I authenticate using the token exchange grant type for impersonation with spring boot and keycloak?
I need to impersonate through token exchange various users and as such I'm going to be needing a new token for each user. So basically the token exchange authentication query is the same each time but with a new requested_subject
parameter. I'm fetching its value for the ClientRequest
's attributes. This looks like this right now:
MonoGraphQLClient.createWithWebClient(WebClient.builder()
.baseUrl(properties.httpUrl)
.filter { request, next ->
val userAttribute: String = request.attribute(REQUESTING_USER_ATTRIBUTE).map(Any::toString).orElseThrow()
ServerOAuth2AuthorizedClientExchangeFilterFunction(
AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository,
clientService
).apply {
setAuthorizedClientProvider(TokenExchangeReactiveOAuth2AuthorizedClientProvider().apply {
// Bypassing subject token presence check in TokenExchangeReactiveOAuth2AuthorizedClientProvider::authorize
setSubjectTokenResolver { Mono.just(OAuth2Token { null }) }
// Overriding client with our more permissive one
setAccessTokenResponseClient(LenientWebClientReactiveTokenExchangeTokenResponseClient().apply {
parametersProvider = {
LinkedMultiValueMap<String, String>().apply {
add("audience", properties.tokenAudience)
add("requested_subject", userAttribute) // This is where I set it
}
}
})
})
}
).also {
// We know we use only one provider, so it's easier and less coupled to the actual value to do things like this
it.setDefaultClientRegistrationId(clientRegistrationRepository.first().registrationId)
}.filter(request, next)
}
.build()
So the issue here is that on each request it reuses the previous token as long as it's not expired as it's being stored by ReactiveOAuth2AuthorizedClientService
. I'd like a new token for each requested_subject.
The thing is, ReactiveOAuth2AuthorizedClientService
's methods are based on the clientRegistrationId
and principalName
and ServerOAuth2AuthorizedClientExchangeFilterFunction
, gets the principalName
from these lines:
Note that the SecurityContext is not present here so we're always falling back to the anonymous user. The reason is that I'm in a service that reacts to events and does graphQL queries to a third party service. It's completely isolated and not publicly exposed and as such there is no authentication to this service.
So I have a couple solutions in my mind but I'm not really satisfied with any of these.
ServerOAuth2AuthorizedClientExchangeFilterFunction
so that it allows me to override the default token to change the principal name (and maybe ask this as an upgrade to spring?).ReactiveOAuth2AuthorizedClientService
per requested_subject
. This would require me to create some kind of factory on top of it but nothing too big.ReactiveSecurityContextHolder
from my ExchangeFilterFunction
but I haven't tried to pursue this option because I'm really not sure how this is going to behave if multiple queries for different users are triggered in parallel.Solution 2 seems the cleanest to me so far.
How can I properly authenticate with a different requested_subject
only when needed and in a spring-friendly way ?
The third option is actually the intended way to override the authentication. It uses the reactive Context
(not a ThreadLocal
like in a servlet application).