Spring Rest Template : Host name 'localhost' does not match the certificate subject provided by the peer

16,429

Solution 1

The correct solution for this problem is to correct the ssl certificate by adding localhost to the list of subjects. However, if your intent is to bypass ssl for development purpose, you would need to define a connection factory which always returns the result of hostname verification as true.

SSLClientHttpRequestFactory

public class SSLClientHttpRequestFactory extends SimpleClientHttpRequestFactory {

    @Override
    protected void prepareConnection(HttpURLConnection connection, String httpMethod) {
        try {
            if (!(connection instanceof HttpsURLConnection)) {
                throw new RuntimeException("An instance of HttpsURLConnection is expected");
            }

            HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;

            TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                    return null;
                }

                public void checkClientTrusted(X509Certificate[] certs, String authType) {
                }

                public void checkServerTrusted(X509Certificate[] certs, String authType) {
                }

            } };
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
            httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory()));

            httpsConnection.setHostnameVerifier(new HostnameVerifier() {
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });

            super.prepareConnection(httpsConnection, httpMethod);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * We need to invoke sslSocket.setEnabledProtocols(new String[] {"SSLv3"});
     * see
     * http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566
     * -2342133.html (Java 8 section)
     */
    private static class MyCustomSSLSocketFactory extends SSLSocketFactory {

        private final SSLSocketFactory delegate;

        public MyCustomSSLSocketFactory(SSLSocketFactory delegate) {
            this.delegate = delegate;
        }

        @Override
        public String[] getDefaultCipherSuites() {
            return delegate.getDefaultCipherSuites();
        }

        @Override
        public String[] getSupportedCipherSuites() {
            return delegate.getSupportedCipherSuites();
        }

        @Override
        public Socket createSocket(final Socket socket, final String host, final int port, final boolean autoClose)
                throws IOException {
            final Socket underlyingSocket = delegate.createSocket(socket, host, port, autoClose);
            return overrideProtocol(underlyingSocket);
        }

        @Override
        public Socket createSocket(final String host, final int port) throws IOException {
            final Socket underlyingSocket = delegate.createSocket(host, port);
            return overrideProtocol(underlyingSocket);
        }

        @Override
        public Socket createSocket(final String host, final int port, final InetAddress localAddress,
                final int localPort) throws IOException {
            final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
            return overrideProtocol(underlyingSocket);
        }

        @Override
        public Socket createSocket(final InetAddress host, final int port) throws IOException {
            final Socket underlyingSocket = delegate.createSocket(host, port);
            return overrideProtocol(underlyingSocket);
        }

        @Override
        public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress,
                final int localPort) throws IOException {
            final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
            return overrideProtocol(underlyingSocket);
        }

        private Socket overrideProtocol(final Socket socket) {
            if (!(socket instanceof SSLSocket)) {
                throw new RuntimeException("An instance of SSLSocket is expected");
            }
            ((SSLSocket) socket).setEnabledProtocols(new String[] { "TLSv1" });
            return socket;
        }
    }
}

And use the above mentioned connection factory as the constructor argument for RestTemplate. The part of the code which overrides the host name verification to always return true is as follows:

httpsConnection.setHostnameVerifier(new HostnameVerifier() {
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });

Happy coding!

Solution 2

As accepted answer has deprecated code, this is what I found helpful:

SSLContextBuilder sslcontext = new SSLContextBuilder();
            sslcontext.loadTrustMaterial(null, new TrustSelfSignedStrategy());
            httpclient = HttpAsyncClients.custom().setSSLContext(sslcontext.build()).setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
                    .build();

Solution 3

Here is how I made it to work: 1. This bean ignores SSL check 2. It also ignores certificate mismatch

@Bean
    public RestTemplate restTemplate()
            throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
        TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;

        SSLContextBuilder sslcontext = new SSLContextBuilder();
        sslcontext.loadTrustMaterial(null, new TrustSelfSignedStrategy());

        SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
                                                               .loadTrustMaterial(null, acceptingTrustStrategy)
                                                               .build();

        SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);

        CloseableHttpClient httpClient = HttpClients.custom().setSSLContext(sslcontext.build()).setSSLHostnameVerifier(
                NoopHostnameVerifier.INSTANCE)
                                                    .build();

        HttpComponentsClientHttpRequestFactory requestFactory =
                new HttpComponentsClientHttpRequestFactory();

        requestFactory.setHttpClient(httpClient);
        RestTemplate restTemplate = new RestTemplate(requestFactory);
        return restTemplate;
    }
Share:
16,429
David Vincent
Author by

David Vincent

Graduated from University Of Surabaya, March 2015. IT Coordinator at Informatics Logical Programming Competition University Of Surabaya (ILPC 2014-2015) : https://ilpc.ifubaya.com/ Working Experience Since April 2015 - Present Programming Language : Php, Java, C#, Html, Css, Javascript, AngularJS, MySQL Framework : CodeIgniter, Spring Position : Fullstack Developer, Software Engineer, Junior Developer, Senior Developer Freelance Job : Binary / Forex Trader

Updated on June 24, 2022

Comments

  • David Vincent
    David Vincent almost 2 years

    I use RestTemplate config like this :

    private RestTemplate createRestTemplate() throws Exception {
            final String username = "admin";
            final String password = "admin";
            final String proxyUrl = "localhost";
            final int port = 443;
    
            CredentialsProvider credsProvider = new BasicCredentialsProvider();
            credsProvider.setCredentials(new AuthScope(proxyUrl, port),
                    new UsernamePasswordCredentials(username, password));
    
            HttpHost host = new HttpHost(proxyUrl, port, "https");
    
            HttpClientBuilder clientBuilder = HttpClientBuilder.create();
    
            clientBuilder.setProxy(host).setDefaultCredentialsProvider(credsProvider).disableCookieManagement();
    
            HttpClient httpClient = clientBuilder.build();
            HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
            factory.setHttpClient(httpClient);
    
            return new RestTemplate(factory);
        }
    

    And the this is how my method work:

    public String receiveMessage(String message) {
            try {
                restTemplate = createRestTemplate();
                ObjectMapper mapper = new ObjectMapper();
                Class1 class1 = null;
                String json2 = "";
    
                class1= mapper.readValue(message, Class1.class);
    
                Class1 class2 = restTemplate.getForObject(URL_SERVICE_1 + "/class1/findByName?name=" + class1.getName(),
                        Class1.class);
                System.out.println("Server 1 : " + message);
                json2 = mapper.writeValueAsString(class2);
    
                return "Error - " + json2;
            } catch (Exception e) {
                // TODO Auto-generated catch block
                return e.getMessage();
            }
    
        }
    

    URL_SERVICE_1 contains https://localhost

    When I tried to call function GET, I always get return like this :

    I/O error on GET request for "https://localhost/class1/findByName?name=20-1P": Host name 'localhost' does not match the certificate subject provided by the peer (CN=*.webku-cool.com, OU=EssentialSSL Wildcard, OU=Domain Control Validated); nested exception is javax.net.ssl.SSLPeerUnverifiedException: Host name 'localhost' does not match the certificate subject provided by the peer (CN=*.webku-cool.com, OU=EssentialSSL Wildcard, OU=Domain Control Validated)
    

    I don't know the correct setting for restTemplate with https. I already tried 23 references about SSL Settings and got same error.

    1. Reference

    2. Reference

    3. Reference