1

I am trying to utilize Spring 5 Webclient and the built in oauth2 security features.

@Bean("oauth2WebClient")
    public WebClient oauth2WebClient(final ReactiveOAuth2AuthorizedClientManager reactiveOAuth2AuthorizedClientManager) {
        final ServerOAuth2AuthorizedClientExchangeFilterFunction serverOAuth2AuthorizedClientExchangeFilterFunction =
                new ServerOAuth2AuthorizedClientExchangeFilterFunction(reactiveOAuth2AuthorizedClientManager);
        serverOAuth2AuthorizedClientExchangeFilterFunction.setDefaultClientRegistrationId("app");
        try {

            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(new FileInputStream(new ClassPathResource("app.jks").getFile()), "password".toCharArray());
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            trustStore.load(new FileInputStream(new ClassPathResource("app.jks").getFile()), "password".toCharArray());

            // Set up key manager factory to use our key store
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(keyStore, "password".toCharArray());

            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(trustStore);

            SslContext sslContext = SslContextBuilder.forClient()
                    .keyManager(keyManagerFactory)
                    .trustManager(trustManagerFactory)
                    .build();
//
//
//            List<Certificate> certificateList = Collections.list(trustStore.aliases())
//                    .stream()
//                    .filter(t -> {
//                        try {
//                            return trustStore.isCertificateEntry(t);
//                        } catch (KeyStoreException e1) {
//                            throw new RuntimeException("Error reading truststore", e1);
//                        }
//                    })
//                    .map(t -> {
//                        try {
//                            return trustStore.getCertificate(t);
//                        } catch (KeyStoreException e2) {
//                            throw new RuntimeException("Error reading truststore", e2);
//                        }
//                    })
//                    .collect(Collectors.toList());
//
//            X509Certificate[] certificates = certificateList.toArray(new X509Certificate[certificateList.size()]);
//            //PrivateKey trustedCerts = (PrivateKey) keyStore.getKey("certs", "password".toCharArray());
//            //PrivateKey privateKey = (PrivateKey) keyStore.getKey("certs", "password".toCharArray());
//            //Certificate[] certChain = keyStore.getCertificateChain("certs");
////            X509Certificate[] x509CertificateChain = Arrays.stream(certChain)
////                    .map(certificate -> (X509Certificate) certificate)
////                    .collect(Collectors.toList())
////                    .toArray(new X509Certificate[certChain.length]);
//
//            SslContext sslContext = SslContextBuilder.forClient()
//                    .keyManager(keyManagerFactory)
//                    .trustManager(certificates)
//                    .build();

            HttpClient httpClient = HttpClient.create()
                    .secure(sslContextSpec -> sslContextSpec.sslContext(sslContext));
            ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
            return WebClient.builder()
                    .filter(serverOAuth2AuthorizedClientExchangeFilterFunction)
                    .clientConnector(connector)
                    .build();

I have confirmed the SSL certs are setup correctly and work when I use the Webclient to get the access token and fire off the service calls using that token.

When I use the built in oauth2 libs, I get the following error:

org.springframework.security.oauth2.client.ClientAuthorizationException: [server_error] 

I see the below in the stack trace



reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    |_ checkpoint ⇢ Request to POST <url>[DefaultWebClient]
Stack trace:


java.lang.Exception: #block terminated with an error

What am I doing wrong here?

dev75
  • 11
  • 2
  • 1
    This error is usually due to either the Certificate is expired, or incorrect. I suggest you debug step by step to see if you are indeed using the correct certificate and that it is not expired. – JCompetence Apr 09 '21 at 14:30
  • Thanks. I confirmed the TLS communication is working if I configure the Webclient myself. It looks like maybe the built in oauth2 impl is not able to recognize the cert even though its there – dev75 Apr 09 '21 at 16:48

1 Answers1

0

In addition to checking the SSL certificates as recommended in the comments of the original post, try double checking the server's response in a debugger.

I was able to see the HTTP response's status code in org.springframework.security.oauth2.core.web.reactive.function.OAuth2AccessTokenResponseBodyExtractor::extract (see inputMessage.response.responseState.response.status)

I was able to see the JSON response body by setting a breakpoint in OAuth2AccessTokenResponseBodyExtractor::parse method.

In my case the problem was obvious. A proxy returned a non-OAuth error message.

To capture the error in my code, I used WebClientReactiveClientCredentialsTokenResponseClient::setWebClient and WebClient.builder().filter() to log the body using this filter:

ExchangeFilterFunction logError = ExchangeFilterFunction.ofResponseProcessor(
        clientResponse -> {
            if (clientResponse.statusCode() != null && !clientResponse.statusCode().isError()) return Mono.just(clientResponse);

            LOGGER.error("OAuth response error: {}", clientResponse.statusCode().toString());

            return clientResponse
                    .bodyToMono(String.class)
                    .doOnNext(s -> {
                        LOGGER.error("OAuth response body: {}", s);
                        LOGGER.error("Note: OAuth response body has been consumed for logging. Spring OAuth client library will report that the response is empty.");
                    })
                    .map(s -> clientResponse);
        });
Alexander Taylor
  • 16,574
  • 14
  • 62
  • 83