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