How to manage HTTPS Mutual Authentication including Bearer Token with Spring boot WebClient?

351 views Asked by At

The goal of my post is to directly share my answer regarding the following topic. I share also the links that helped me => I'm developping a backend based on Spring webflux. A frontEnd angular application is connected to my backend. My backend is also connected to a business server through HTTPS based on mutual authentication. I have to set a bearer token in each header request to my business server. I retrieve this token from a server through OAuth 2.0 client credentials grant flow through HTTPS. My answer below shows how I managed the implementation.

1

There are 1 answers

0
cknelle On BEST ANSWER

Thanx to following links :

I found out the following solution :

@Configuration
@EnableWebFluxSecurity
public class Oauth2ClientConfig {

    // Bearer Token management based on OAuth 2.0 client credentials grant flow
    @Bean
    ReactiveClientRegistrationRepository getRegistration() {
        ClientRegistration registration = ClientRegistration
                .withRegistrationId("credentialProvider")
                .tokenUri("https://rhsso:8446/auth/token")
                .clientId("client_id").clientSecret("client_secret")
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .build();
        return new InMemoryReactiveClientRegistrationRepository(registration);
    }
    // Bearer Token management using HTTPS
    @Bean
    public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
            ReactiveClientRegistrationRepository clientRegistrationRepository,
            ReactiveOAuth2AuthorizedClientService authorizedClientService) throws IOException, KeyStoreException,
            NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException {

        WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient = new WebClientReactiveClientCredentialsTokenResponseClient();
              
        final FileInputStream keyStoreFile = new FileInputStream("C:\\keystore\\client_keystore.p12"); // client private key and client certificate
        final FileInputStream trustStoreFile = new FileInputStream("C:\\truststore\\server_truststore.p12");
        final KeyStore keyStore = KeyStore.getInstance("PKCS12");
        final KeyStore trustStore = KeyStore.getInstance("PKCS12");
        final KeyManagerFactory keyManagerFactory = KeyManagerFactory
                .getInstance(KeyManagerFactory.getDefaultAlgorithm());
        final TrustManagerFactory trustManagerFactory = TrustManagerFactory
                .getInstance(TrustManagerFactory.getDefaultAlgorithm());

        String keyStorePassword = "keyStorePassword";
        String trustStorePassword = "trustStorePassword";
        keyStore.load(keyStoreFile, keyStorePassword.toCharArray());
        keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
        trustStore.load(trustStoreFile, trustStorePassword.toCharArray());
        trustManagerFactory.init(trustStore);
        
        // HTTPS management through sslContext
        SslContextBuilder sslContextBuilder = SslContextBuilder.forClient().keyManager(keyManagerFactory)
                .trustManager(trustManagerFactory);

        SslContext sslContext = sslContextBuilder.build();

        HttpClient httpSslClient = HttpClient.create().secure(ssl -> ssl.sslContext(sslContext));

        ClientHttpConnector httpConnector = new ReactorClientHttpConnector(httpSslClient);

        accessTokenResponseClient.setWebClient(WebClient.builder().clientConnector(httpConnector).build()); 

        ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder
                .builder().clientCredentials(c -> {
                    c.accessTokenResponseClient(accessTokenResponseClient);
                }).build();

        
        AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
                clientRegistrationRepository, authorizedClientService);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    }

    @Bean("CustomWebClient") //Qualifier to be used by my dedicated ClientAPI service in order to use the right one among available WebClient beans
    public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) throws KeyStoreException,
            NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException {
        ServerOAuth2AuthorizedClientExchangeFilterFunction oauthFunction = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
                authorizedClientManager);
        oauthFunction.setDefaultClientRegistrationId("credentialProvider"); // Link to Bearer token managed above through Its ID

        final FileInputStream keyStoreFile = new FileInputStream("C:\\keystore\\business_client_keystore.p12"); // client private key and client certificate
        final FileInputStream trustStoreFile = new FileInputStream("C:\\truststore\\business_server_truststore.p12");
        final KeyStore keyStore = KeyStore.getInstance("PKCS12");
        final KeyStore trustStore = KeyStore.getInstance("PKCS12");
        final KeyManagerFactory keyManagerFactory = KeyManagerFactory
                .getInstance(KeyManagerFactory.getDefaultAlgorithm());
        final TrustManagerFactory trustManagerFactory = TrustManagerFactory
                .getInstance(TrustManagerFactory.getDefaultAlgorithm());

        String keyStorePassword = "business_keyStorePassword";
        String trustStorePassword = "business_trustStorePassword";
        keyStore.load(keyStoreFile, keyStorePassword.toCharArray());
        keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
        trustStore.load(trustStoreFile, trustStorePassword.toCharArray());

        trustManagerFactory.init(trustStore);
        // Create Netty SslContext, HttpClient
        SslContextBuilder sslContextBuilder = SslContextBuilder.forClient().keyManager(keyManagerFactory)
                .trustManager(trustManagerFactory);

        SslContext sslContext = sslContextBuilder.build();

        HttpClient httpSslClient = HttpClient.create().secure(ssl -> ssl.sslContext(sslContext));

        // Create Reactive ClientHttpConnector & WebClient
        final ClientHttpConnector httpConnector = new ReactorClientHttpConnector(httpSslClient);

        return WebClient.builder().clientConnector(httpConnector).filter(oauthFunction).build();

    }

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        // disable csrf with frontend interface but generally not recommended otherwise only in special cases
        // cors management in order to accept FrontEnd application installed localhost and only POST method is allowed 
        http.csrf().disable().cors(corsCustomizer -> {
            CorsConfigurationSource configurationSource = request->{
                CorsConfiguration corsConfiguration = new CorsConfiguration();
                corsConfiguration.setAllowedOrigins(List.of("localhost:8080"));
                corsConfiguration.setAllowedMethods(List.of("POST"));
                return corsConfiguration;
            };
            corsCustomizer.configurationSource(configurationSource);
        }).oauth2Client(); 
        
        return http.build();
    }

}

My webclient based on my beans above :

@Service
public class ClientAPI {
    

    private WebClient webClient;
    private String businessServiceUrl;

    @Autowired
    public ClientAPI(@Qualifier("CustomWebClient") WebClient webClient) {
        this.businessServiceUrl = "https://hostServerName:8081/theService";
        this.webClient = webClient;
    }

    public Mono<Response> sendRequest(Request bodyRequest) {

        Mono<Response> result = this.webClient.post().uri(this.businessServiceUrl)
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .body(Mono.just(bodyRequest), Request.class).retrieve().bodyToMono(Response.class);

        return result;
    }

}