Search code examples
spring-boothttpsspring-webfluxbearer-tokenmutual-authentication

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


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.


Solution

  • 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;
        }
    
    }