I'm trying to find examples of WebClient use.
My goal is to use Spring 5 WebClient to query a REST service using https and self signed certificate
Any example?
I'm trying to find examples of WebClient use.
My goal is to use Spring 5 WebClient to query a REST service using https and self signed certificate
Any example?
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.
@Bean
public 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;
@Component
public class ClientComponent {
public ClientComponent(WebClient.Builder webClientBuilder, @Value("${url}") String url) {
this.client = webClientBuilder.baseUrl(solrUrl).build();
}
}
This one work for me. Spring framework version 5.3.23 (Spring boot version 2.7.4) You can try this way using ReactorClientHttpConnector:
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();
hope this answer is helpful for you.
For someone who might have stuck on how to consume a https protected REST API with reactive WebFlux webClient
You want to create two things
Note: please go through above project look at keystore which is shared with Both the above spring boot apps. And programmatically added the keyManagerFactory and TrustManagerFactory.
Also, if we need to configure multiple SSLContexts, for example, we have REST API 1 and REST API 2 and for them we configured SSLContext sslContext1
and SSLContext sslContext2
The thing is that HttpClient.create().secure(...)
allows us to apply only one SSLContext
, but in our case we want multiple.
So, the solution in our case would be to create two different WebClient
with different ReactorClientHttpConnector
// for REST API 1 with sslContext1
WebClient webClient1 = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(
HttpClient.create()
.secure(sslContextSpec -> sslContextSpec.sslContext(sslContext1))
))
.build();
// for REST API 1 with sslContext2
WebClient webClient2 = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(
HttpClient.create()
.secure(sslContextSpec -> sslContextSpec.sslContext(sslContext2))
))
.build();
And we're done!
Also, just to point out, these clients will share the event loop group by default, which is recommended. However, they won't, if you configure them using runOn
or with using ReactorResourceFactory
, more about resources can be found here: https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-client-builder-reactor-resources