Why does RestTemplate always report "Hostname mismatch" when calling HTTPS? CertificateException root cause analysis

Preface

When using RestTemplate to call the API, we may encounter errors such as java.security.cert.CertificateException: No name matching.

Cause Analysis

The java.security.cert.CertificateException: No name matching error is essentially a symptom of an SSL certificate verification failure. When RestTemplate calls an API over HTTPS, it verifies the SSL certificate returned by the server. The hostname (Common Name, CN) or Subject Alternative Name (SAN) in the certificate must match the hostname of the API being called. If they do not match, this error is triggered. This is a measure taken by Java's SSL/TLS mechanism to ensure communication security and prevent security risks such as man-in-the-middle attacks.

Workaround

Method 1: Ignore SSL certificate verification (for development environments only)

Create an SSLContext that trusts all certificates and apply it to the RestTemplate. The specific code is as follows:

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate() throws Exception {
        // 创建信任所有证书的SSLContext
        SSLContext sslContext = SSLContext.getInstance("TLS");
        TrustManager[] trustAllCerts = new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                }
        };
        sslContext.init(null, trustAllCerts, new java.security.SecureRandom());

        // 创建HostnameVerifier,信任所有主机名
        HostnameVerifier hostnameVerifier = (s, sslSession) -> true;

        // 配置RestTemplate
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
        requestFactory.setConnectTimeout(30000);
        requestFactory.setReadTimeout(30000);

        return new RestTemplate(requestFactory);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • twenty one.
  • twenty two.
  • twenty three.
  • twenty four.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.

Method 2: Configure the correct SSL certificate (for production environments)

  • Obtain the correct SSL certificate: Obtain an SSL certificate from the API service provider that contains the correct host name (CN or SAN), usually in .cer or .pem format.
  • Import the certificate into the trust store: Use Java's keytool tool to import the certificate into the Java trust store. The command is as follows:
keytool -import -alias apiCert -file /path/to/certificate.cer -keystore $JAVA_HOME/jre/lib/security/cacerts
  • 1.

When executing this command, you need to enter the default password of the trust store, changeit

  • Configure RestTemplate to use a trust store: When creating a RestTemplate, specify the trust store containing the correct certificate. The code is as follows:
@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate() throws Exception {
        // 加载信任库
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        FileInputStream fis = new FileInputStream("/path/to/truststore/apiCert.jks");
        trustStore.load(fis, "truststorePassword".toCharArray());
        fis.close();

        // 初始化TrustManagerFactory
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(trustStore);

        // 创建SSLContext
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, tmf.getTrustManagers(), new java.security.SecureRandom());

        // 配置RestTemplate
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
        requestFactory.setConnectTimeout(30000);
        requestFactory.setReadTimeout(30000);

        return new RestTemplate(requestFactory);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • twenty one.
  • twenty two.
  • twenty three.
  • twenty four.
  • 25.
  • 26.
  • 27.
  • 28.

Summarize

RestTemplate is compatible with both HTTP and HTTPS protocols because it automatically selects the appropriate processing logic based on the requested URL protocol (http or https). When the request is HTTP, the SSL certificate verification process is not triggered, and data transmission is carried out directly according to HTTP communication. When the request is HTTPS, the configured SSLContext and other related parameters are used to verify the certificate and encrypt communication.

In a production environment, in order to be more flexible and compatible with the two protocols, we can further optimize the configuration of RestTemplate and use HttpComponentsClientHttpRequestFactory instead of SimpleClientHttpRequestFactory, which has better support for HTTP and HTTPS.

public class RestTemplateConfig {

    public RestTemplate restTemplate() throws Exception {
        // 加载信任库
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        trustStore.load(new FileInputStream("path/to/truststore"), "truststorePassword".toCharArray());

        // 构建SSLContext
        SSLContext sslContext = SSLContextBuilder.create()
                .loadTrustMaterial(trustStore, null)
                .build();

        // 创建SSL连接套接字工厂
        SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext);

        // 创建HttpClient
        HttpClient httpClient = HttpClients.custom()
                .setSSLSocketFactory(sslSocketFactory)
                .build();

        // 配置请求工厂
        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
        requestFactory.setConnectTimeout(30000);
        requestFactory.setReadTimeout(30000);

        return new RestTemplate(requestFactory);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • twenty one.
  • twenty two.
  • twenty three.
  • twenty four.
  • 25.
  • 26.
  • 27.
  • 28.

You can also use SimpleClientHttpRequestFactory to implement protocol adaptive processing. The specific steps are as follows:

public class DualProtocolRequestFactory extends SimpleClientHttpRequestFactory {

    @Override
    protected void prepareConnection(HttpURLConnection connection, String httpMethod) {
        try {
            // HTTP请求直接处理
            if (!(connection instanceof HttpsURLConnection)) {
                super.prepareConnection(connection, httpMethod);
                return;
            }

            // HTTPS请求跳过证书验证(仅测试环境)
            HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new TrustManager[]{new BlindTrustManager()}, null);
            httpsConnection.setSSLSocketFactory(sslContext.getSocketFactory());
            httpsConnection.setHostnameVerifier((hostname, session) -> true); // 禁用主机名验证

            super.prepareConnection(httpsConnection, httpMethod);
        } catch (Exception e) {
            throw new RuntimeException("HTTPS配置失败", e);
        }
    }

    private static class BlindTrustManager implements X509TrustManager {
        public X509Certificate[] getAcceptedIssuers() { return null; }
        public void checkClientTrusted(X509Certificate[] certs, String authType) {}
        public void checkServerTrusted(X509Certificate[] certs, String authType) {}
    }
}