资讯专栏INFORMATION COLUMN

关于java访问https资源时,忽略证书信任问题

songjz / 2522人阅读

摘要:程序在访问资源时,出现报错这本质上,是在访问资源时的证书信任问题。因此,如果用访问资源,发现证书不可信任,则会报文章开头说到的错误。

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)先实现验证方法

HostnameVerifier hv = new HostnameVerifier() {
        public boolean verify(String urlHostName, SSLSession session) {
            System.out.println("Warning: URL Host: " + urlHostName + " vs. "
                               + session.getPeerHost());
            return true;
        }
    };
 
 private static void trustAllHttpsCertificates() throws Exception {
 javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];
 javax.net.ssl.TrustManager tm = new miTM();
 trustAllCerts[0] = tm;
 javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext
 .getInstance("SSL");
 sc.init(null, trustAllCerts, null);
 javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(sc
 .getSocketFactory());
 }
 
 static class miTM implements javax.net.ssl.TrustManager,
 javax.net.ssl.X509TrustManager {
 public java.security.cert.X509Certificate[] getAcceptedIssuers() {
 return null;
 }
 
 public boolean isServerTrusted(
 java.security.cert.X509Certificate[] certs) {
 return true;
 }
 
 public boolean isClientTrusted(
 java.security.cert.X509Certificate[] certs) {
 return true;
 }
 
 public void checkServerTrusted(
 java.security.cert.X509Certificate[] certs, String authType)
 throws java.security.cert.CertificateException {
 return;
 }
 
 public void checkClientTrusted(
 java.security.cert.X509Certificate[] certs, String authType)
 throws java.security.cert.CertificateException {
 return;
 }
}

2)在访问https资源前,调用

trustAllHttpsCertificates();
HttpsURLConnection.setDefaultHostnameVerifier(hv);

以下是一个具体的例子:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;

import org.apache.log4j.Logger;
import org.htmlparser.util.ParserException;

import com.xwtech.parser.GetRequestHtmlParser;
import com.xwtech.pojo.ExtendCandidate;
/*
 * GET请求类
 */
public class GetRequest {
    private String url = "https://b2b.10086.cn/b2b/main/viewNoticeContent.html?noticeBean.id=";
    private Logger logger;
    public GetRequest() {
        logger = Logger.getLogger(GetRequest.class);
    }
    private static void trustAllHttpsCertificates() throws Exception {
        javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];
        javax.net.ssl.TrustManager tm = new miTM();
        trustAllCerts[0] = tm;
        javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, null);
        javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    }
    public void getData(String id) {
        this.url = url + id;
        BufferedReader in = null;
        HttpURLConnection conn = null;
        String result = "";
        try {
        //该部分必须在获取connection前调用
            trustAllHttpsCertificates();
            HostnameVerifier hv = new HostnameVerifier() {
                public boolean verify(String urlHostName, SSLSession session) {
                    logger.info("Warning: URL Host: " + urlHostName + " vs. " + session.getPeerHost());
                    return true;
                }
            };
            HttpsURLConnection.setDefaultHostnameVerifier(hv);
            conn = (HttpURLConnection)new URL(url).openConnection();
            // 发送GET请求必须设置如下两行
            conn.setDoInput(true);
            conn.setRequestMethod("GET");
            // flush输出流的缓冲
            in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            logger.error("发送 GET 请求出现异常!	请求ID:"+id+"
"+e.getMessage()+"
");
        } finally {// 使用finally块来关闭输出流、输入流
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                logger.error("关闭数据流出错了!
"+ex.getMessage()+"
");
            }
        }
        // 获得相应结果result,可以直接处理......
        
    }
    static class miTM implements javax.net.ssl.TrustManager, javax.net.ssl.X509TrustManager {
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        public boolean isServerTrusted(java.security.cert.X509Certificate[] certs) {
            return true;
        }

        public boolean isClientTrusted(java.security.cert.X509Certificate[] certs) {
            return true;
        }

        public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
                throws java.security.cert.CertificateException {
            return;
        }

        public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
                throws java.security.cert.CertificateException {
            return;
        }
    }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73806.html

相关文章

  • nginx配置ssl加密(单/双向认证、部分https

    摘要:设置协商加密算法时,优先使用我们服务端的加密套件,而不是客户端浏览器的加密套件。 nginx下配置ssl本来是很简单的,无论是去认证中心买SSL安全证书还是自签署证书,但最近公司OA的一个需求,得以有个机会实际折腾一番。一开始采用的是全站加密,所有访问http:80的请求强制转换(rewrite)到https,后来自动化测试结果说响应速度太慢,https比http慢慢30倍,心想怎么可...

    kviccn 评论0 收藏0
  • 如何在Tomcat中做TLS客户端认证

    摘要:顺便一提,如果要使用客户端认证就必须使用服务端认证。修改,添加如下可以看到我们开启了客户端认证,也开启了服务端认证。所以如果要正确访问得像下面这样,指定证书,以及客户端自己签发的证书及测试我们现在用来访问看看。 常见的https网站做的是服务端认证(server authentication),浏览器通过证书判断你所访问的https://baidu.com是否真的是百度,而不是其他人伪...

    sugarmo 评论0 收藏0
  • spy-debugger + Charles 移动端调试

    摘要:简介移动端调试一直都是一个痛点,因为移动终端对于我们来说是一个黑盒,它无法像端一样,我们可以通过很方便的调出开发者工具。如果没有调试工具这种情况下我们就很难定位问题,接下来的主题就是介绍如何使用进行移动端调试。 简介 ​ 移动端调试一直都是一个痛点,因为移动终端对于我们来说是一个黑盒,它无法像PC端一样,我们可以通过F12很方便的调出开发者工具。在开发中经常会遇到同样一份...

    fevin 评论0 收藏0

发表评论

0条评论

songjz

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<