• JAVA报错:java.security.cert.CertificateException: No name matching
  • 发布于 6小时前
  • 9 热度
    0 评论

前言
在使用RestTemplate调用 API的过程中,我们可能会遇到java.security.cert.CertificateException: No name matching这样的错误。

原因解析
java.security.cert.CertificateException: No name matching错误本质上是SSL证书验证失败的一种表现。当RestTemplate通过HTTPS协议调用API时,会对服务器返回的SSL证书进行验证。其中,证书中的主机名(Common Name,简称 CN)或主题备用名称(Subject Alternative Name,简称 SAN)需要与我们实际调用的API的主机名相匹配。如果不匹配,就会触发该错误,这是Java的SSL/TLS机制为了保障通信安全而采取的措施,防止中间人攻击等安全风险。

解决方法
方法一:忽略 SSL 证书验证(仅适用于开发环境)
创建一个信任所有证书的SSLContext,并将其应用到RestTemplate中。具体代码如下:
@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;
        // 堆代码 duidaima.com
        // 配置RestTemplate
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
        requestFactory.setConnectTimeout(30000);
        requestFactory.setReadTimeout(30000);

        return new RestTemplate(requestFactory);
    }
}
方法二:配置正确的 SSL 证书(适用于生产环境)
获取正确的SSL证书:从API服务提供商处获取包含正确主机名(CN或SAN)的 SSL 证书,通常为.cer或.pem格式。
导入证书到信任库:使用Java的keytool工具将证书导入到Java的信任库中。命令如下:
keytool -import -alias apiCert -file /path/to/certificate.cer -keystore $JAVA_HOME/jre/lib/security/cacerts
注意:执行该命令时,需要输入信任库的默认密码changeit

配置RestTemplate使用信任库:在创建RestTemplate时,指定使用包含正确证书的信任库。代码如下:
@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);
    }
}
总结
RestTemplate能同时兼容HTTP和HTTPS协议,是因为RestTemplate底层会依据请求的URL协议(http或https)自动选择对应的处理逻辑。当请求为HTTP时,不会触发SSL证书验证相关的流程,直接按照HTTP的通信方式进行数据传输;当请求为HTTPS时,才会运用我们配置的SSLContext等相关参数进行证书验证和加密通信。

在生产环境中,为了更灵活地兼容两种协议,我们可以对RestTemplate的配置进行进一步优化,使用HttpComponentsClientHttpRequestFactory替代SimpleClientHttpRequestFactory,它对HTTP和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);
    }
}
也可以SimpleClientHttpRequestFactory实现协议自适应处理,具体步骤如下:
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) {}
    }
}

用户评论