使用RestTemplate进行接口转发,转发地址是 https://内网IP
的格式,由于证书是自签的,所以在调试的时候
接口报错,在jvm中无法找到匹配的SSL证书:
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target] with root cause
所以在浏览器访问 https://内网IP
,并通过浏览器将证书下载下来,安装到我们的JDK:
# 进入到安装的jre目录,并将下载下来的证书拷贝到当前目录 cd $JAVA_HOME/jre/lib/security # 执行安装命令 keytool -import -alias 证书别名 -keystore cacerts -file ./xxxx.cer
经过上面的操作后重启服务,调用接口发现还是报同样的错误,但是SSL证书确实是安装成功的,所以索性在服务生成RestTemplate的Bean时绕过SSL验证,一般情况下,会通过SimpleClientHttpRequestFactory来构建RestTemplate实例,但是它不支持https,要支持https需要替换SimpleClientHttpRequestFactory为HttpComponentsClientHttpRequestFactory,而且我们还要引入httpclient依赖:
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.6</version> </dependency>
修改RestTemplateConfig配置类:
@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate(ClientHttpRequestFactory factory){ return new RestTemplate(factory); } @Bean public ClientHttpRequestFactory httpComponentsClientHttpRequestFactory() throws Exception { // 自定义证书校验器 TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) { } @Override public void checkServerTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) { } @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } } }; SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext); CloseableHttpClient httpClient = HttpClients.custom() .setSSLSocketFactory(csf) .build(); HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); factory.setConnectTimeout(20000); factory.setReadTimeout(15000); factory.setHttpClient(httpClient); return factory; } }
重启服务调用接口,原以为应该没什么大问题了,可是报了不同的错误:
Certificate for <10.xx.xx.xx> doesn't match any of the subject alternative names: [localhost, xx.xx.com]
从上面的错误提示可以看出,其实第一步安装的证书是成功的,但是在创建SSL连接验证域名的时候出错了,打开证书查看证书保护的域名只有 localhost, xx.xx.com
,所以这是报错的根源所在,我通过IP访问的链接,但是IP不在证书保护的域名内,导致验证错误,但是我通过域名访问的链接 https://域名
是成功的。
网上查找了原因,我引入的httpclient版本是4.4以上的,所以在生成SSLConnectionSocketFactory实例时,需要指定host验证器,允许ssl连接允许通过不同的hosts,
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
完整的RestTemplateConfig配置类:
@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate(ClientHttpRequestFactory factory){ return new RestTemplate(factory); } @Bean public ClientHttpRequestFactory httpComponentsClientHttpRequestFactory() throws Exception { // 自定义证书校验器 TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) { } @Override public void checkServerTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) { } @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } } }; SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); CloseableHttpClient httpClient = HttpClients.custom() .setSSLSocketFactory(csf) .build(); HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); factory.setConnectTimeout(20000); factory.setReadTimeout(15000); factory.setHttpClient(httpClient); return factory; } }