Spring 5 WebClient using ssl Spring 5 WebClient using ssl spring spring

Spring 5 WebClient using ssl


Looks like Spring 5.1.1 (Spring boot 2.1.0) removed HttpClientOptions from ReactorClientHttpConnector, so you can not configure options while creating instance of ReactorClientHttpConnector

One option that works now is:

val sslContext = SslContextBuilder            .forClient()            .trustManager(InsecureTrustManagerFactory.INSTANCE)            .build()val httpClient = HttpClient.create().secure { t -> t.sslContext(sslContext) }val webClient = WebClient.builder().clientConnector(ReactorClientHttpConnector(httpClient)).build()

Basically while creating the HttpClient, we are configuring the insecure sslContext, and then passing this httpClient for use in ReactorClientHttpConnector globally.

The other option is to configure TcpClient with insecure sslContext and use it to create HttpClient instance, as illustrated below:

val sslContext = SslContextBuilder            .forClient()            .trustManager(InsecureTrustManagerFactory.INSTANCE)            .build()val tcpClient = TcpClient.create().secure { sslProviderBuilder -> sslProviderBuilder.sslContext(sslContext) }val httpClient = HttpClient.from(tcpClient)val webClient =  WebClient.builder().clientConnector(ReactorClientHttpConnector(httpClient)).build()

For more information:

Update: Java version of the same code

SslContext context = SslContextBuilder.forClient()    .trustManager(InsecureTrustManagerFactory.INSTANCE)    .build();                HttpClient httpClient = HttpClient.create().secure(t -> t.sslContext(context));WebClient wc = WebClient                    .builder()                    .clientConnector(new ReactorClientHttpConnector(httpClient)).build();


See example of use insecure TrustManagerFactory that trusts all X.509 certificates (including self-signed) without any verification. The important note from documentation:

Never use this TrustManagerFactory in production. It is purely for testing purposes, and thus it is very insecure.

@Beanpublic WebClient createWebClient() throws SSLException {    SslContext sslContext = SslContextBuilder            .forClient()            .trustManager(InsecureTrustManagerFactory.INSTANCE)            .build();    ClientHttpConnector httpConnector = HttpClient.create().secure(t -> t.sslContext(sslContext) )    return WebClient.builder().clientConnector(httpConnector).build();}


Had to edit this, to accommodate spring-boot 2.0->2.1 changes.

Another way, if you want to program production code is, to create a spring bean like such, that modifies the injected WebClient, using the settings from the spring-boot server for where the truststore and Keystore are. In the client, you only need to give the Keystore, if you are using 2-way-ssl. Not sure, why the ssl-stuff is not preconfigured and easily injectable, similar to the really cool spring-boot server settings.

import io.netty.handler.ssl.SslContext;import io.netty.handler.ssl.SslContextBuilder;...  @Bean  WebClientCustomizer configureWebclient(@Value("${server.ssl.trust-store}") String trustStorePath, @Value("${server.ssl.trust-store-password}") String trustStorePass,      @Value("${server.ssl.key-store}") String keyStorePath, @Value("${server.ssl.key-store-password}") String keyStorePass, @Value("${server.ssl.key-alias}") String keyAlias) {      return (WebClient.Builder webClientBuilder) -> {          SslContext sslContext;          final PrivateKey privateKey;          final X509Certificate[] certificates;          try {            final KeyStore trustStore;            final KeyStore keyStore;            trustStore = KeyStore.getInstance(KeyStore.getDefaultType());            trustStore.load(new FileInputStream(ResourceUtils.getFile(trustStorePath)), trustStorePass.toCharArray());            keyStore = KeyStore.getInstance(KeyStore.getDefaultType());            keyStore.load(new FileInputStream(ResourceUtils.getFile(keyStorePath)), keyStorePass.toCharArray());            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());            certificates = certificateList.toArray(new X509Certificate[certificateList.size()]);            privateKey = (PrivateKey) keyStore.getKey(keyAlias, keyStorePass.toCharArray());            Certificate[] certChain = keyStore.getCertificateChain(keyAlias);            X509Certificate[] x509CertificateChain = Arrays.stream(certChain)                .map(certificate -> (X509Certificate) certificate)                .collect(Collectors.toList())                .toArray(new X509Certificate[certChain.length]);            sslContext = SslContextBuilder.forClient()                .keyManager(privateKey, keyStorePass, x509CertificateChain)                .trustManager(certificates)                .build();              HttpClient httpClient = HttpClient.create()                .secure(sslContextSpec -> sslContextSpec.sslContext(sslContext));            ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);            webClientBuilder.clientConnector(connector);          } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException | UnrecoverableKeyException e) {            throw new RuntimeException(e);          }        };  }

Here the part, where you use the Webclient:

import org.springframework.web.reactive.function.client.WebClient;@Componentpublic class ClientComponent {  public ClientComponent(WebClient.Builder webClientBuilder, @Value("${url}") String url) {    this.client = webClientBuilder.baseUrl(solrUrl).build();  }}