前两天我维护的服务出了一个问题,在请求一个连接时发生了Exception:
1 2 3 4 5 6 |
Exception in thread "main" javax.net.ssl.SSLPeerUnverifiedException: Host name 'talkingdata.qccr.com' does not match the certificate subject provided by the peer (CN=*.qichechaoren.com, OU=研发部, O=特维轮网络科技(杭州)有限公司, L=杭州, ST=浙江, C=CN) at org.apache.http.conn.ssl.SSLConnectionSocketFactory.verifyHostname(SSLConnectionSocketFactory.java:465) at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:395) at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:353) at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:134) at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353) |
异常大致说的是在进行https请求时,验证证书出了问题。
服务基于JDK1.6,使用httpclient(版本是4.5.1)发送请求,调用的URL是https://talkingdata.qccr.com。
异常中的信息说的是请求的地址(talkingdata.qccr.com)与获得的证书中的hostName(*.qichechaoren.com)不符,因此在SSLPeerUnverifiedException.verifyHostname()方法中抛出了异常。但是在浏览器中发送请求或者是通过curl命令都是能够访问的。最让人郁闷的是在我的机器上使用原程序执行调用也没有报异常;把相关的程序封装成jar扔到服务器上调用还是正常的。
想了一段时间,在curl命令中添加了SSL信息,像这样:
1 2 |
curl -2 https://talkingdata.qccr.com curl -3 https://talkingdata.qccr.com |
2和3分别表示用的是sslv2和sslv3。果然报错了。后来又发现了SSL的实现还有TLS,试着使用TLS协议请求是正确的:
1 |
curl -1 https://talkingdata.qccr.com |
之前没有想到过SSL协议这里,打开浏览器看了一下这个地址的证书果然是TLS的:
看样子是在处理TLS请求时出了问题。
接下来就是定位问题的出处了。既然程序是可以正常执行的,那么就极有可能是出在容器上。把相关的程序重新封装下扔到jetty中执行果然报异常了。当时认为问题就是出在jetty上了,可能是服务与容器间存在类冲突。容器的类是不好轻易换的,只好改程序了。原程序是这样子的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
package com.zhyea.robin; import org.apache.http.client.methods.HttpGet; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.TrustStrategy; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.ssl.SSLContextBuilder; import javax.net.ssl.SSLContext; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; public class HttpClient { public static String get(String url) throws IOException, URISyntaxException { CloseableHttpClient httpClient = createSSLClientDefault(); HttpGet get = new HttpGet(); get.setURI(new URI(url)); return httpClient.execute(get).toString(); } private static CloseableHttpClient createSSLClientDefault() { try { SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() { //信任所有 public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { return true; } }).build(); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext); return HttpClients.custom().setSSLSocketFactory(sslsf).build(); } catch (KeyManagementException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (KeyStoreException e) { e.printStackTrace(); } return HttpClients.createDefault(); } public static void main(String[] args) throws IOException, URISyntaxException { System.out.println(get("https://talkingdata.qccr.com")); } } |
需要调整的就是SSL的支持那一块。将对https的验证强行去掉好了。只需要改一处:
1 2 3 4 5 6 |
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new HostnameVerifier() { @Override public boolean verify(String s, SSLSession sslSession) { return true; } }); |
现在请求是可以不报异常了。因为所有关于hostName的验证都是返回true
隔天和同事说起这件事,同事提供了一个信息:生产环境上的jetty容器使用的jdk和系统jre是不一样的….
jetty容器使用的是jdk1.6,服务器jre是1.7,我开发用的机器jdk是1.8。当时居然没有想到这上头。问题有可能是出在jre上啊。所以最后将jetty使用的jdk版本升级以后就好了。
今天也尝试着定位问题发生的具体位置来着,后来发现是在handshake的时候,jdk1.6和jdk1.7会返回不同的证书信息。更具体的信息想要获取就有些困难了,因为相关的部分代码是不开源的。
之前本以为是这个地址的证书是采用的TLS1.2,而jdk1.6的大部分版本都是不支持TLS1.2的。后来使用工具分析了请求的地址,发现支持的协议包括TLS1.0、TLS1.1、TLS1.2。而TLS1.0在JDK1.1的时候就已经支持了。
已经在这个问题上用了四分之一个周末的时间了,还有其他事情要做,目前只好暂时放下了。
一个SSL分析的网址:https://www.ssllabs.com/ssltest/analyze.html。
openJdk关于TLS1.2的BUG清单:https://bugs.openjdk.java.net/browse/JDK-6916074。
##############
发表评论