前言
在使用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) {}
}
}