David Vincent David Vincent - 26 days ago 15
Java Question

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

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

Answer

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!