Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to disable hostname verification in spring webclient?

Tags:

java

I am using the spring webflux webclient tool to call the API. The API server address is HTTPS, and it is an IP address without a domain name. I need to disable the hostname validation in webclient. The exception now is as follows

Caused by: java.security.cert.CertificateException: No subject alternative names matching IP address 180.101.147.89 found
    at sun.security.util.HostnameChecker.matchIP(HostnameChecker.java:168) ~[na:1.8.0_211]
    at sun.security.util.HostnameChecker.match(HostnameChecker.java:94) ~[na:1.8.0_211]
    at sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:461) ~[na:1.8.0_211]
    at sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:442) ~[na:1.8.0_211]
    at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:260) ~[na:1.8.0_211]
    at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:144) ~[na:1.8.0_211]
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1626) ~[na:1.8.0_211]
    ... 28 common frames omitted
@Bean
    public WebClient telcomWebclient(WebClient.Builder webClientBuilder,
                                     @Value("${telcom.api.host}") String telcomApiHost,
                                     @Value("${telcom.api.certificate-name}") String telcomApiCertificateName,
                                     @Value("${telcom.api.certificate-store-pass}") String telcomApiCertificateStorePass) {
        try {
            KeyStore selfCert = KeyStore.getInstance("pkcs12");
            selfCert.load(getClass().getResourceAsStream("/cert/outgoing.CertwithKey.pkcs12"), "IoM@1234".toCharArray());
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("sunx509");
            kmf.init(selfCert, "IoM@1234".toCharArray());

            KeyStore caCert = KeyStore.getInstance("jks");
            caCert.load(getClass().getResourceAsStream("/cert/" + telcomApiCertificateName), telcomApiCertificateStorePass.toCharArray());
            TrustManagerFactory tmf = TrustManagerFactory.getInstance("sunx509");
            tmf.init(caCert);

            SslContext sslContext = SslContextBuilder.forClient()
                    .keyManager(kmf)
                    .trustManager(tmf)
                    .build();
            HttpClient httpClient = HttpClient.create().create().secure(sslContextSpec -> sslContextSpec.sslContext(sslContext));
            ClientHttpConnector clientHttpConnector = new ReactorClientHttpConnector(httpClient);
            return webClientBuilder.clientConnector(clientHttpConnector).baseUrl(telcomApiHost).build();
        } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException e) {
            log.error("Config webclient,error occurs", e);
            System.exit(-1);
        }
        return null;
    }
like image 611
wangyongjun Avatar asked May 17 '19 10:05

wangyongjun


People also ask

How disable SSL hostname verification?

Click the name of the server for which you want to disable host name verification. Select Configuration > SSL , and click Advanced at the bottom of the page. Set the Hostname Verification field to None.

What is WebClient spring boot?

Simply put, WebClient is an interface representing the main entry point for performing web requests. It was created as part of the Spring Web Reactive module and will be replacing the classic RestTemplate in these scenarios.


2 Answers

Aside from disabling SSL verification entirely, (WHICH I DON'T RECOMMEND) by passing in InsecureTrustManagerFactory.INSTANCE like this:

SslContext sslContext = SslContextBuilder.forClient()
                    .keyManager(kmf)
                    .trustManager(InsecureTrustManagerFactory.INSTANCE)
                    .build();

You can configure the HttpClient to essentially override the hostname verification by configuring a custom SNIMatcher as below:

HttpClient.create().create().secure(sslContextSpec -> sslContextSpec
    .sslContext(sslContext)
    .handlerConfigurator(sslHandler -> 
        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);
);

I have tested this and verified it worked. I hope this helps!

This was inspired by the answer here: Configure HostnameVerifier with reactor netty for spring-webflux WebClient

like image 193
James Avatar answered Oct 17 '22 07:10

James


Actually SNIMatcher is for server side only. This can be verified by putting a break point on the return of the matches() method. Execution never stops at that break point.
The actual reason the host name verification is skipped because the newly params.identificationAlgorithm value is null instead of "HTTPS" when the SSLParameters is overriden.
Here is a simpler code which works as well as above.

           HttpClient.create().secure(sslContextSpec -> sslContextSpec
                   .sslContext(sslContext)
                   .handlerConfigurator(sslHandler -> {
                       SSLEngine engine = handler.engine();
                       SSLParameters params = new SSLParameters(engine.getSSLParameters().getCipherSuites(), engine.getSSLParameters().getProtocols());
                       // With java update null value is no longer sufficient to skip host name verification
                       params.setEndpointIdentificationAlgorithm("");
                       engine.setSSLParameters(params);
                   }));
like image 39
hung nguyen Avatar answered Oct 17 '22 07:10

hung nguyen