• Java访问HTTPS时证书验证问题


    为了尽可能避免安全问题,公司的很多系统服务都逐步https化,虽然开始过程会遇到各种问题,但趋势不改。最完美的https应用是能实现双向认证,客户端用私钥签名用服务端公钥加密,服务端用私钥签名客户端都公钥加密,但现实很多情况不可能让每个客户端都申请一个证书,因此只实现https的单项认证,即只要服务端又证书,客户端只验证https端证书可靠就可进行https通信。在某些情况下为了不花钱买第三方信任机构颁发都证书,客户端在一些情况下也不做服务器端都认证,两边只实现htts的加密通信。最近就遇到一个问题,https调用证书验证失败,最终考虑还是忽略调服务证书的验证。

    java程序在访问https资源时,出现报错
    sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    这本质上,是java在访问https资源时的证书信任问题。如何解决这个问题呢?
     
    为何有这个问题?
    解决这个问题前,要了解
    1)https通信过程
    客户端在使用HTTPS方式与Web服务器通信时有以下几个步骤,如图所示。
    (1)客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。
    (2)Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。
    (3)客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
    (4)客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
    (5)Web服务器利用自己的私钥解密出会话密钥。
    (6)Web服务器利用会话密钥加密与客户端之间的通信。
     
     
    2)java程序的证书信任规则
    如上文所述,客户端会从服务端拿到证书信息。调用端(客户端)会有一个证书信任列表,拿到证书信息后,会判断该证书是否可信任。
    如果是用浏览器访问https资源,发现证书不可信任,一般会弹框告诉用户,对方的证书不可信任,是否继续之类。
    Java虚拟机并不直接使用操作系统的keyring,而是有自己的security manager。与操作系统类似,jdk的security manager默认有一堆的根证书信任。如果你的https站点证书是花钱申请的,被这些根证书所信任,那使用java来访问此https站点会非常方便。因此,如果用java访问https资源,发现证书不可信任,则会报文章开头说到的错误。
     
    解决问题的方法
    1)将证书导入到jdk的信任证书中(理论上应该可行,未验证)
    2)在客户端(调用端)添加逻辑,忽略证书信任问题
    第一种方法,需要在每台运行该java程序的机器上,都做导入操作,不方便部署,因此,采用第二种方法。下面贴下该方法对应的代码。
     
    验证可行的代码
    1)先实现验证方法
    [java] view plain copy
     
    1. HostnameVerifier hv = new HostnameVerifier() {  
    2.         public boolean verify(String urlHostName, SSLSession session) {  
    3.             System.out.println("Warning: URL Host: " + urlHostName + " vs. "  
    4.                                + session.getPeerHost());  
    5.             return true;  
    6.         }  
    7.     };  
    8.    
    9.  private static void trustAllHttpsCertificates() throws Exception {  
    10.  javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];  
    11.  javax.net.ssl.TrustManager tm = new miTM();  
    12.  trustAllCerts[0] = tm;  
    13.  javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext  
    14.  .getInstance("SSL");  
    15.  sc.init(null, trustAllCerts, null);  
    16.  javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(sc  
    17.  .getSocketFactory());  
    18.  }  
    19.   
    20.  static class miTM implements javax.net.ssl.TrustManager,  
    21.  javax.net.ssl.X509TrustManager {  
    22.  public java.security.cert.X509Certificate[] getAcceptedIssuers() {  
    23.  return null;  
    24.  }  
    25.   
    26.  public boolean isServerTrusted(  
    27.  java.security.cert.X509Certificate[] certs) {  
    28.  return true;  
    29.  }  
    30.   
    31.  public boolean isClientTrusted(  
    32.  java.security.cert.X509Certificate[] certs) {  
    33.  return true;  
    34.  }  
    35.   
    36.  public void checkServerTrusted(  
    37.  java.security.cert.X509Certificate[] certs, String authType)  
    38.  throws java.security.cert.CertificateException {  
    39.  return;  
    40.  }  
    41.   
    42.  public void checkClientTrusted(  
    43.  java.security.cert.X509Certificate[] certs, String authType)  
    44.  throws java.security.cert.CertificateException {  
    45.  return;  
    46.  }  
    47.  }  
    2)在访问https资源前,调用
    [java] view plain copy
     
    1. trustAllHttpsCertificates();  
    2. HttpsURLConnection.setDefaultHostnameVerifier(hv);  

    http://blog.csdn.net/lizeyang/article/details/18983843

    解决https证书验证不通过的问题

    1、报错信息

    java.security.cert.CertificateException: No name matching api.weibo.com found; nested exception is javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No name matching api.weibo.com found

      原因:在调用api.weibo.com的时候,我们使用的是https的方式,正常情况下应该是使用api.weibo.com的证书,但由于某些原因,我们只能使用自己的证书,导致在验证证书的时候,就报了这个错误。

      解决的办法:忽略服务端和客户端的证书校验即可。java 提供的相关的类。

    2、具体实现方式

      通过重写TrustManager的checkClientTrusted(检查客户端证书信任)和checkServerTrusted(检查服务端证书验证)。

      以及HostnameVerifier的verify(校验)方法即可取消对证书的所有验证。

    复制代码
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import javax.net.ssl.*;
    import java.io.IOException;
    import java.net.URL;
    import java.security.cert.CertificateException;
    import java.security.cert.X509Certificate;
    
    public final class DisableSSLCertificateCheckUtil {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(DisableSSLCertificateCheckUtil.class);
    
        /**
         * Prevent instantiation of utility class.
         */
    
        private DisableSSLCertificateCheckUtil() {
    
        }
    
        /**
         * Disable trust checks for SSL connections.
         */
    
        public static void disableChecks() {
            try {
                new URL("https://0.0.0.0/").getContent();
            } catch (IOException e) {
                // This invocation will always fail, but it will register the
                // default SSL provider to the URL class.
            }
            try {
                SSLContext sslc;
                sslc = SSLContext.getInstance("TLS");
                TrustManager[] trustManagerArray = {new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    
                    }
    
                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    
                    }
    
                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }
                }};
                sslc.init(null, trustManagerArray, null);
                HttpsURLConnection.setDefaultSSLSocketFactory(sslc.getSocketFactory());
                HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String s, SSLSession sslSession) {
                        return true;
                    }
                });
            } catch (Exception e) {
                LOGGER.error("error msg:{}", e);
                throw new IllegalArgumentException("证书校验异常!");
            }
        }
    }
    复制代码

      调用方式:

    DisableSSLCertificateCheckUtil.disableChecks();

      影响的范围:将会影响整个tomcat里面对证书的验证。即通过tomcat里面的其他项目虽然没有执行这一段代码但是也同样会忽略证书的验证。

      影响的时间:执行这段代码之后的所有时间都生效。

  • 相关阅读:
    sqlserver游标使用误区
    工作笔记——sqlserver引号的运用
    疯狂JAVA——数组
    工厂模式、单例和多例
    数据库数据交互详解(一)
    2016-4-6
    2016-4-5 博问问题、答题和查看收获
    Maven+Spring Batch+Apache Commons VF学习
    你忽视的静态类的作用(必看)
    Wireshark抓包工具使用教程以及常用抓包规则
  • 原文地址:https://www.cnblogs.com/doit8791/p/7684926.html
Copyright © 2020-2023  润新知