The requirement is to call an OAuth Protected endpoint from a java client program using WebClient. I'm using Password Grant authorization type using Reactive objects. Please note I'm very novice to reactive programming mode. When I make the webclient call I get the following error ** serverWebExchange cannot be null **
Please let me know if the below configuration and usage is correct and/or how do I initialize the serverWebExchange object.
String data = webClient
.post().uri(endPoint)
// This will add the Authorization header with the bearer token.
.attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId("custom"))
.body(Mono.just(alert), Alert.class)
.retrieve()
.bodyToMono(String.class)
// Block until we receive a response for a non-reactive client.
.block();
java.lang.IllegalArgumentException: serverWebExchange cannot be null
at org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager.lambda$loadAuthorizedClient$9(DefaultReactiveOAuth2AuthorizedClientManager.java:102)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ Request to POST http://<hostname>/<protected_endpoint> [DefaultWebClient]
Stack trace:
at org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager.lambda$loadAuthorizedClient$9(DefaultReactiveOAuth2AuthorizedClientManager.java:102)
at reactor.core.publisher.MonoErrorSupplied.subscribe(MonoErrorSupplied.java:70)
at reactor.core.publisher.Mono.subscribe(Mono.java:4210)
at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:75)
.....
at reactor.core.publisher.Mono.block(Mono.java:1665)
at com.xxxxx.oauthclient.ApplicationTests.testWebClient(ApplicationTests.java:41)
....
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Suppressed: java.lang.Exception: #block terminated with an error
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:99)
at reactor.core.publisher.Mono.block(Mono.java:1666)
at com.xxxx.oauthclient.ApplicationTests.testWebClient(ApplicationTests.java:41)
....
I have used the following configuration
@Configuration
public class ReactiveOAuthConfig {
@Value("${oauth.username}") String username;
@Value("${oauth.password}") String password;
@Bean
WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.filter(oauth)
.build();
}
@Bean
ReactiveOAuth2AuthorizedClientManager reactiveOAuth2AuthorizedClientManager(
ReactiveClientRegistrationRepository clientRegistrationRepository,
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.password()
.build();
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
// For the `password` grant, the `username` and `password` are supplied via request parameters,
// so map it to `OAuth2AuthorizationContext.getAttributes()`.
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());
return authorizedClientManager;
}
private Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttributesMapper() {
return authorizeRequest -> {
Map<String, Object> contextAttributes = Collections.emptyMap();
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
contextAttributes = new HashMap<>();
// `PasswordOAuth2AuthorizedClientProvider` requires both attributes
contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
}
return Mono.just(contextAttributes);
};
}
application properties look like this
spring.security.oauth2.client.provider.custom.token-uri=https://endpoint_to_get_token
spring.security.oauth2.client.registration.custom.client-id=<clientid>
spring.security.oauth2.client.registration.custom.client-secret=<client-secret>
spring.security.oauth2.client.registration.custom.client-authentication-method=post
spring.security.oauth2.client.registration.custom.authorization-grant-type=password
spring.security.oauth2.client.registration.custom.scope=AppIdClaimsTrust
Versions: spring boot version: 2.2.6.RELEASE <spring-security.version>5.2.2.RELEASE</spring-security.version>
Note: I was able to validate calling Oauth Protected endpoint from a web client as documented in this https://github.com/jgrandja/spring-security-oauth-5-2-migrate/tree/master/client-app