Enhancement
- Allow extension of
AbstractWebClientReactiveOAuth2AccessTokenResponseClientby class outside the package. - Allow extension of
BodyInserters.FormInserter<String> populateTokenRequestBody(T grantRequest, BodyInserters.FormInserter<String> body)by classes outside the package.
OR
Provide some other way to customize the request made by token response clients.
Context
In servlet, we have the ability to provide a Converter such as Converter<OAuth2ClientCredentialsGrantRequest, RequestEntity<?>> to customize the creation of the request ultimately sent to an authorization server for a token.
Such customization is not yet available in the Reactive arena. Token response clients are all abstracted from a package-protected abstract class. There is no method available to extend in a way that allows me to do the same as above. All potential methods are located in the abstract class and are themselves package-private.
This customization is especially useful when dealing with different OAuth2 authorization servers that want additional data. One such provider is Active Directory where you may provide a resource field in the request.
Looking at the token response clients, I see there is no request object to customize, so the next best option would be customizing the token client. This looks to be possible if classes were made extensible. This allow us to customize in a pretty straightforward way:
public class WebClientReactiveAdfsClientCredentialsTokenResponseClient
extends WebClientReactiveClientCredentialsTokenResponseClient {
String resource;
@Override
protected BodyInserters.FormInserter<String> populateTokenRequestBody(
OAuth2ClientCredentialsGrantRequest grantRequest, BodyInserters.FormInserter<String> body) {
// Populate request body as normal
super.populateTokenRequestBody(grantRequest, body)
// populate my "resource" value for Active Directory
body.with("resource", resource);
return body;
}
}
Comment From: jjstreet
Barring any concerns over the proposed solution, I can put a PR together with the above changes.
Comment From: jgrandja
@jjstreet WebClient allows you to modify the ClientRequest and/or ClientResponse via an ExchangeFilterFunction. See ExchangeFilterFunction.ofRequestProcessor() for processing the outgoing ClientRequest and ExchangeFilterFunction.ofResponseProcessor for processing the incoming ClientResponse. You can then configure WebClient with the custom ExchangeFilterFunction and supply it to the ReactiveOAuth2AccessTokenResponseClient implementation. Please see this comment for additional details.
Closing this as duplicate of #8612
Comment From: jjstreet
I read through #8612 and this is totally a duplicate of that. I didn't search thoroughly enough it seems.
I am in the same situation as #8612. I need to use client registration id to look up a resource value and apply to the request. Much like Auth0 requires additional fields, so does active directory. In my example I show the token client not being reusable for different registrations, but it would be by passing along a repository of resource registrations (like I plan to). I left it out of the example for the sake of brevity. I should have left it in.
Just so I understand your recommended approach:
- Use a delegate token client after setting up a context:
@Override
public Mono<OAuth2AccessTokenResponse> getTokenResponse(OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) {
return Mono.subscriberContext()
.map(ctx -> {
ctx.put(AdfsResourceReactorContext.RESOURCE, "resource");
return ctx;
})
.flatMap(ctx -> delegate.getTokenResponse(authorizationGrantRequest));
}
Not sure if this correct, but I can read up on reactor context to understand it a bit more.
- Create an
ExchangeFilterFunctionto use the context values in a way to alter the request body.
This is where I am now struggling to understand your recommended approach. ExchangeFilterFunction.ofRequestProcesssor() takes a Function and gives me a ClientHttpRequest but what do I do with it? Can I use ClientHttpRequest.body() to alter the request to my needs? Would I have to cast the inserter somehow? Just really confused on this stage of the approach.
- Add the filter to a new webclient and set delegate token client to use this webclient
Pretty straight forward here. I can do that.
Comment From: jgrandja
@jjstreet
Can I use
ClientHttpRequest.body()to alter the request to my needs
Yes.
Assuming you want to modify the default token request for a client_credentials client, you would configure as follows:
private ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsTokenResponseClient() {
WebClient webClient = WebClient.builder().filter(clientCredentialsTokenRequestProcessor()).build();
WebClientReactiveClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient =
new WebClientReactiveClientCredentialsTokenResponseClient();
clientCredentialsTokenResponseClient.setWebClient(webClient);
return clientCredentialsTokenResponseClient;
}
private static ExchangeFilterFunction clientCredentialsTokenRequestProcessor() {
return ExchangeFilterFunction.ofRequestProcessor(request ->
Mono.just(ClientRequest.from(request)
.body(((BodyInserters.FormInserter<String>) request.body())
.with("resource", "resource1"))
.build()
)
);
}
Comment From: jjstreet
The casting is kind of a bummer, but I'm definitely gonna give this a shot and see how it works.
Thanks for the followup advice. Really appreciate it.