I am building a service that will allow a user to associate multiple oauth identities to their account and then retrieve information based on any / all of the identities.
I am storing the oauth identities in Postgres using Spring's provided R2dbcReactiveOAuth2AuthorizedClientService
. My current challenge is to associate the saved oauth identity to a WebClient so the information is based on that Oauth identity.
Based on the JavaDoc for ServerOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient
, I can pass the OAuth2AuthorizedClient
and it will use that identity for the WebClient.retrieve()
.
Modifies the ClientRequest.attributes() to include the OAuth2AuthorizedClient to be used for providing the Bearer Token. Example usage:
WebClient webClient = WebClient.builder()
.filter(new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager))
.build();
Mono<String> response = webClient
.get()
.uri(uri)
.attributes(oauth2AuthorizedClient(authorizedClient))
// ...
.retrieve()
.bodyToMono(String.class);
Based on debugging, my current code successfully loads the oauth identity from the database and adds it as an attribute to the WebClient. When the WebClient retrieves, I get the error IllegalArgumentException: serverWebExchange cannot be null
. The other questions on SO that refer to this error indicate that it happens when you mix servlet and reactive calls. However, I only have WebFlux as a maven dependency, so I'm pretty sure that is not happening here.
Any suggestions on how to resolve / proceed?
My product service
public class ProductService {
private final ReactiveOAuth2AuthorizedClientService oAuth2AuthorizedClientService;
private final ReactiveClientRegistrationRepository clientRegistrations;
private static final String baseUri = "https://myapp.net/product";
public ProductService(ReactiveOAuth2AuthorizedClientService oAuth2AuthorizedClientService,
ReactiveClientRegistrationRepository clientRegistrations) {
this.oAuth2AuthorizedClientService = oAuth2AuthorizedClientService;
this.clientRegistrations = clientRegistrations;
}
public Mono<String> getNotifications(String productName, String userName) {
String dataUri = "/{id}/notifications";
Mono<OAuth2AuthorizedClient> userOauth = oAuth2AuthorizedClientService.loadAuthorizedClient("xxx", userName);
Mono<Long> productId = this.lookupProductId(productName);
return Mono.zip(productId, userOauth).checkpoint().flatMap(tuple2 ->
this.getUserWebClient(userName).get()
.uri(uriBuilder -> uriBuilder
.path(dataUri)
.queryParam("datasource", "development")
.build(tuple2.getT1().toString()))
.attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient(tuple2.getT2()))
.retrieve()
.bodyToMono(String.class));
}
private WebClient getUserWebClient() {
var authorizedClients = new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(oAuth2AuthorizedClientService);
var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
clientRegistrations, authorizedClients);
return WebClient.builder()
.baseUrl(baseUri)
.filter(oauth)
.build();
}
public Mono<Long> lookupProductId(String name) {
// business logic to lookup product based on name
}
}
Web Security configuration to use Postgres repository instead of the default In-Memory bean
@Bean
public ReactiveOAuth2AuthorizedClientService dbOauth2AuthorizedClientService(DatabaseClient databaseClient,
ReactiveClientRegistrationRepository clientRegistrationRepository) {
return new R2dbcReactiveOAuth2AuthorizedClientService(databaseClient, clientRegistrationRepository);
}
After a nice long summer holiday, I found and re-read the docs at https://docs.spring.io/spring-security/reference/reactive/oauth2/client/core.html. This lead me to understand that the error is due to not having a ResponseContext
since it is in a @Service
component. For the time being, I moved it to a @GetMapping
in a @Controller
.
The docs point out that a AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager
can be used in a @Service
component. I will try to update this answer with the code for that once I have it complete.