Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Configure HostnameVerifier with reactor netty for spring-webflux WebClient

I'm trying to configure spring-webflux WebClient (with reactor netty under the hood) with ssl and client hostname verification. I'm provided with javax.net.ssl.SSLContext, HostnameVerifier and a list of trusted hostnames (as string list).

So far I've configured WebClient with my SSLContext, but I can't find a way to configure hostname verification.

To state my problem: I have a set of trusted services hostnames (String list) and a HostnameVerifier. I want to configure my WebClient with it.

Is there a possibility to do it with the javax.net.ssl.HostnameVerifier? Is there an alternative approach in reactor netty?

This is what I've got so far:

WebClient.builder()
  .clientConnector(
    new ReactorClientHttpConnector(
      opt -> opt.sslContext(new JdkSslContext(mySSLContext, 
                      true, ClientAuth.OPTIONAL))))
  .build();
like image 355
Gosia Avatar asked Jul 26 '18 15:07

Gosia


People also ask

Does spring WebFlux use Netty?

For reactive stack applications, the spring-boot-starter-webflux includes Reactor Netty by including spring-boot-starter-reactor-netty , but you can use spring-boot-starter-tomcat , spring-boot-starter-jetty , or spring-boot-starter-undertow instead.

Does WebClient use Netty?

WebClient is a reactive and non-blocking interface for HTTP requests, based on Spring WebFlux. It has a functional, fluent API with reactive types for declarative composition. Behind the scenes, WebClient calls an HTTP client. Reactor Netty is the default and reactive HttpClient of Jetty is also supported.

Is WebClient part of WebFlux?

WebClient is part of Spring 5's reactive web framework called Spring WebFlux. To use WebClient, you need to include the spring-webflux module in your project.


2 Answers

You should provide a valid certificate authority certificate (trustManager()) and optionally user cert with private key and private key password for authorization (keyManager()). Your service SSL certificate should be signed by the same CA that you've defined in trustManager().

Hostnames are being verified automatically with service hostname. If there's not match java.security.cert.CertificateException: No subject alternative names present exception will be thrown. Actually I can't find a way to omit hostnames verification (without omitting whole SSL certificate verification by using .trustManager(InsecureTrustManagerFactory.INSTANCE)).

I've tested this solution locally. My webservice is running on my local machine but its SSL certificate contains only DNS name, not the IP address. So for my debug purposes I've added entry to hosts file and mapped my service IP to proper DNS name.

SslContext sslContext = SslContextBuilder
        .forClient()
        .trustManager(new FileInputStream(caPath))
        .keyManager(
                new FileInputStream(userCertPath),
                new FileInputStream(userPrivateKeyPath),
                userPrivateKeyPassword
        )
        .build();

HttpClient httpClient = HttpClient.create()
        .secure(t -> t.sslContext(sslContext));

WebClient webClient = WebClient.builder()
        .clientConnector(new ReactorClientHttpConnector(httpClient))
        .build();
like image 139
Michał Stochmal Avatar answered Sep 27 '22 01:09

Michał Stochmal


I've tried following solution with Netty HttpClient and it worked as well (Disabling the hostname verification by using a custom hostname matcher)

public HttpClient getHttpClient(HttpClientProperties properties){

            // configure pool resources
            HttpClientProperties.Pool pool = properties.getPool();

            ConnectionProvider connectionProvider;
            if (pool.getType() == DISABLED) {
                connectionProvider = ConnectionProvider.newConnection();
            }
            else if (pool.getType() == FIXED) {
                connectionProvider = ConnectionProvider.fixed(pool.getName(),
                        pool.getMaxConnections(), pool.getAcquireTimeout());
            }
            else {
                connectionProvider = ConnectionProvider.elastic(pool.getName());
            }

            HttpClient httpClient = HttpClient.create(connectionProvider)
                    .tcpConfiguration(tcpClient -> {

                        if (properties.getConnectTimeout() != null) {
                            tcpClient = tcpClient.option(
                                    ChannelOption.CONNECT_TIMEOUT_MILLIS,
                                    properties.getConnectTimeout());
                        }

                        // configure proxy if proxy host is set.
                        HttpClientProperties.Proxy proxy = properties.getProxy();

                        if (StringUtils.hasText(proxy.getHost())) {

                            tcpClient = tcpClient.proxy(proxySpec -> {
                                ProxyProvider.Builder builder = proxySpec
                                        .type(ProxyProvider.Proxy.HTTP)
                                        .host(proxy.getHost());

                                PropertyMapper map = PropertyMapper.get();

                                map.from(proxy::getPort).whenNonNull().to(builder::port);
                                map.from(proxy::getUsername).whenHasText()
                                        .to(builder::username);
                                map.from(proxy::getPassword).whenHasText()
                                        .to(password -> builder.password(s -> password));
                                map.from(proxy::getNonProxyHostsPattern).whenHasText()
                                        .to(builder::nonProxyHosts);
                            });
                        }
                        return tcpClient;
                    });

            HttpClientProperties.Ssl ssl = properties.getSsl();
            if (ssl.getTrustedX509CertificatesForTrustManager().length > 0
                    || ssl.isUseInsecureTrustManager()) {
                httpClient = httpClient.secure(sslContextSpec -> {
                    // configure ssl
                    SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();

                    X509Certificate[] trustedX509Certificates = ssl
                            .getTrustedX509CertificatesForTrustManager();
                    if (trustedX509Certificates.length > 0) {
                        sslContextBuilder.trustManager(trustedX509Certificates);
                    }
                    else if (ssl.isUseInsecureTrustManager()) {
                        sslContextBuilder
                                .trustManager(InsecureTrustManagerFactory.INSTANCE);
                    }


                    sslContextSpec.sslContext(sslContextBuilder)
                            .defaultConfiguration(ssl.getDefaultConfigurationType())
                            .handshakeTimeout(ssl.getHandshakeTimeout())
                            .closeNotifyFlushTimeout(ssl.getCloseNotifyFlushTimeout())
                            .closeNotifyReadTimeout(ssl.getCloseNotifyReadTimeout())
                            .handlerConfigurator(
                                    (handler)->{
                                        SSLEngine engine = handler.engine();
                                        //engine.setNeedClientAuth(true);
                                        SSLParameters params = new SSLParameters();
                                        List<SNIMatcher> matchers = new LinkedList<>();
                                        SNIMatcher matcher = new SNIMatcher(0) {

                                            @Override
                                            public boolean matches(SNIServerName serverName) {
                                                return true;
                                            }
                                        };
                                        matchers.add(matcher);
                                        params.setSNIMatchers(matchers);
                                        engine.setSSLParameters(params);
                                    }
                            )
                    ;
                });
            }

            return httpClient;

        }

It uses nettys handlerConfigurator to configure SSLEngine and use it with custom matchers

like image 22
Venky Avatar answered Sep 23 '22 01:09

Venky