I have 2 services A & B which should communicate over with each other over HTTPS. I have enabled TLS using server.ssl.* properties of Spring boot for both the applications. I am using WebClient for the communication where Service A will call B using the webclient and B would send a response back to A. For communication over TLS my understanding is that the Webclient would need the truststore data which would have the certiifcates of the service that is being called i.e. Service B.
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManagerFactory;
import org.springframework.boot.web.server.Ssl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import lombok.extern.slf4j.Slf4j;
import reactor.netty.http.client.HttpClient;
@Slf4j
@Configuration
public class WebClientConfig {
private final Ssl ssl;
public WebClientConfig(final Ssl ssl) {
this.ssl = ssl;
}
@Bean
public WebClient createWebClient() throws Exception {
if (ssl.isEnabled()) {
return buildSslEnabledWebClient();
}
return WebClient.builder().build();
}
private WebClient buildSslEnabledWebClient() throws Exception {
final String trustStorePath = ssl.getTrustStore();
final String trustStorePassword = ssl.getTrustStorePassword();
final KeyStore trustStore = createKeyStore(trustStorePath, trustStorePassword);
try {
final TrustManagerFactory trustManager = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManager.init(trustStore);
final SslContext sslContext = SslContextBuilder.forClient().trustManager(trustManager)
.build();
final HttpClient httpClient = HttpClient.create().secure(ssl -> {
ssl.sslContext(sslContext);
});
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient)).build();
} catch (NoSuchAlgorithmException | KeyStoreException | SSLException e) {
log.error("Could not initialize Webclient with the trustore data", e);
throw e;
}
}
private static KeyStore createKeyStore(final String keyStoreLocation, final String keyStorePassword) {
try (FileInputStream fis = new FileInputStream(keyStoreLocation)) {
final KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(fis, keyStorePassword.toCharArray());
return ks;
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
throw new IllegalArgumentException(e);
}
}
}
I have created self signed certificates using the keytool and this seems to be working. I could make a call from A to B and the response back.
However I somehow feel that the setup is not complete and hence my questions are :
- I am missing something?
- Is this the correct way to do it?
- Will this is work in production with real certificates?
UPDATE : Fixed the code, realized the mistake after reading a below answer. I am actually using the updated code.