一、前述
使用 httpclient 发起 https 请求时,可能会遇到如下异常:
- javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
- at sun.security.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:397)
- at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:128)
- at org.apache.http.conn.ssl.SSLSocketFactory.createSocket(SSLSocketFactory.java:399)
- at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:143)
网上搜索也能找到一大堆的解决方案,但大部分都类似,就是跳过证书的验证,于是跟着稀里糊涂的将代码拷贝下来使用,结果呢?有的能解决,有的依旧报这个错误。到底咋回事呢,接下来就说说这个问题的解决方案。
二、缘由
首先,要知道导致报这个异常的原因不仅仅是因为证书校验不通过。
都知道,在我们通过 https 链接服务器时,服务器会给我们返回一个证书,这个证书可能经过 CA 认证,也可能是未认证的自制证书,客户端拿到这个证书后会对这个证书进行验证,如果是经过 CA 验证的证书,自然证书校验就能通过,自制证书自然就校验不同过,从而导致上边的异常。
证书校验通过后,还需要校验访问的域名是否和证书指定的域名是否匹配。未匹配也会导致如上异常。
上边两步都校验通过了才开始进行握手,但握手也有可能失败,从而导致上边的异常。
以上三个步骤中任何一个出了问题,都会连接失败。
三、解决方法
通过网上搜索我们大部分都会找到类似如下的解决方案:
- SSLContext sslContext;
- try {
- sslContext = SSLContext.getInstance("SSL");
- // set up a TrustManager that trusts everything
- try {
- sslContext.init(null,
- new TrustManager[] { new X509TrustManager() {
- public X509Certificate[] getAcceptedIssuers() {
- return null;
- }
- public void checkClientTrusted(
- X509Certificate[] certs, String authType) {
- }
- public void checkServerTrusted(
- X509Certificate[] certs, String authType) {
- }
- } }, new SecureRandom());
- } catch (KeyManagementException e) {
- }
- SSLSocketFactory ssf = new SSLSocketFactory(sslContext,SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
这个解决方案针对以上的三个步骤中的第一步,就是放弃对证书的校验,但是第二部还可能有问题,要想彻底解决还需要跳过对域名的校验。这里给出最终的解决方案:
- try {
- SSLContext sslContext = SSLContext.getInstance("TLS");
- X509TrustManager tm = 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,new TrustManager[]{tm},null);
- sslSocketFactory = new SSLSocketFactory(sslContext,new X509HostnameVerifier(){
- @Override
- public boolean verify(String s, SSLSession sslSession) {
- return true;
- }
- @Override
- public void verify(String host, SSLSocket ssl) throws IOException {
- }
- @Override
- public void verify(String host, X509Certificate cert) throws SSLException {
- }
- @Override
- public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
- }
- });
- } catch (GeneralSecurityException e) {
- log.error("create SSLSocketFactory error:{}",e);
- }
- return sslSocketFactory;
这里不仅放弃对证书的校验,也放弃对 hostname 的校验,通过空实现 X509HostnameVerifier 类。
以上这个解决方案就会彻底解决问题吗?不一定,还有一个步骤就是握手的步骤也可能出问题,怎么判断是不是握手步骤出了问题呢?可以在代码里做如下设置:
- System.setProperty("javax.net.debug","ssl");
做了以上设置后,就可以打印 https 建立连接的日志了,如下:
通过最后一个词组 "handshake_failure",你一定可以确定是握手失败了。这一般是因为客户端的加密机制太简单,服务器认为不安全,握手失败。
握手失败解决方案就比较简单,下载一个 UnlimitedJCEPolicyJDK7.zip 。在 http://www.oracle.com/technetwork/java/javase/downloads/index.html 下载就好了。里面包含了两个 jar。在你的 / lib/security,替换后,重新运行看看。
如果以上这些还没能解决,那我也是不知道了,可以留言,我跟你一块找答案 ^_^。
来源: