当前位置: 首页 > news >正文

UniHttp/Jsoup Https SSL证书验证失败:SunCertPathBuilderException解决方案详解

目录

前言

一、场景再现

1、要做什么事

2、使用Java调用接口

二、Jsoup的实现与问题解决

1、使用Jsoup来访问第三方接口

2、Jsoup解决访问Https问题的方法

三、UniHttp的实现与问题解决

1、UniHttp定义第三方接口

2、UniHttp调用接口实战

3、UniHttp解决Https的访问问题

四、总结


前言

        在当今微服务架构与数据驱动的开发范式中,HTTP客户端已成为Java应用不可或缺的基础设施。无论是构建高性能的RESTful API调用,还是开发数据采集与内容解析工具,我们习惯依赖UniHttp(及其背后的OkHttp/Spring RestTemplate技术栈)和Jsoup这类成熟库来简化网络通信的复杂度。然而,当请求的目标URL从http://切换到https://时,一个看似简单的协议升级却常常成为开发旅程中的"暗礁"——SunCertPathBuilderException这个晦涩的异常信息,就像一道无形的数字证书壁垒,将无数开发者挡在安全的HTTPS世界之外。

        这个异常最令人生畏之处,在于它的出现往往伴随着项目进度的戛然而止。想象一下这样的场景:你的爬虫程序在测试环境中对目标网站解析完美,一旦部署到生产服务器,Jsoup.connect()却抛出javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target;或者你的微服务在调用第三方支付接口时,UniHttp客户端突然报告证书路径构建失败。

        本文的目标读者是正在与SSL证书问题搏斗的Java开发者——无论你是需要快速修复本地开发环境阻塞,还是负责设计生产级安全通信架构,都能在这里找到清晰的技术路径。后续章节将从异常堆栈的深度解读开始,逐步展开证书链验证的底层原理。让我们一同揭开HTTPS证书验证的神秘面纱,将这个令人沮丧的异常转化为深入理解Java安全体系的机会。

一、场景再现

        本节我们将讲述一下我们需要使用Java来实现一个什么需求。我们以国家统计局为例,我们需要使用互联网接口来获取查询相应的数据,而网站的协议是Https,那么如何获取Https的接口数据呢,下面来看看调用https接口时,可能会遇到什么问题。        

1、要做什么事

        统计数据相信大家都会遇到,比如如下官网网站:

        可以在浏览器的网络栏中,看到以下链接:

https://data.stats.gov.cn/easyquery.htm?m=QueryData&dbcode=fsyd&rowcode=zb&colcode=sj&wds=%5B%7B%22wdcode%22%3A%22reg%22%2C%22valuecode%22%3A%22110000%22%7D%5D&dfwds=%5B%7B%22wdcode%22%3A%22zb%22%2C%22valuecode%22%3A%22A030102%22%7D%5D&k1=1692174996701&h=1

        将以上连接复制到浏览器中,可以看到以下输出:

        如果我们想实现动态的参数传入和数据获取,使用Java语言可以有哪些方式来获取Https接口返回的数据呢?且看下文说明。

2、使用Java调用接口

        在Java中,要实现第三方接口的调用,有很多种实现方式,比如使用原生的网络接口,也可以使用HttpClient这样的组件,与HttpClient相类似还有Jsoup和UniHttp,Jsoup也是一个非常常见的第三方接口访问组件;而UniHttp则是方便快速的进行接口集成的组件。以Jsoup为例,当我们使用Jsoup编写完了代码之后,在访问Https接口时,就很容易发生以下异常:

        报错异常信息如下:

Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested targetat sun.security.validator.PKIXValidator.doBuild(Unknown Source)at sun.security.validator.PKIXValidator.engineValidate(Unknown Source)at sun.security.validator.Validator.validate(Unknown Source)at sun.security.ssl.X509TrustManagerImpl.validate(Unknown Source)at sun.security.ssl.X509TrustManagerImpl.checkTrusted(Unknown Source)at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(Unknown Source)... 16 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested targetat sun.security.provider.certpath.SunCertPathBuilder.build(Unknown Source)at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(Unknown Source)at java.security.cert.CertPathBuilder.build(Unknown Source)... 22 more

        从技术本质上看,SunCertPathBuilderException并非某个库的缺陷,而是Java安全模型正确运作的体现。当客户端发起HTTPS请求时,JVM会触发一套严格的证书链验证机制:它不仅要验证服务器证书的数字签名是否有效、域名是否匹配,更要构建一条从服务器证书到可信根证书颁发机构(CA)的完整信任链。这个过程中,如果任何一个中间证书缺失、根证书不在JVM的信任库(cacerts)中、或者证书已过期失效,Sun security provider就会抛出该异常。在Java 8之后,虽然sun包下的类被标记为内部API,但这个异常类名依然保留,成为开发者诊断SSL问题的关键线索。问题的复杂性还在于应用场景的多样性。

二、Jsoup的实现与问题解决

        对于Jsoup而言,其典型场景是网络爬虫与HTML解析,目标网站参差不齐——既有使用Let's Encrypt免费证书的正规站点,也有配置错误缺失中间证书的小众网站,甚至还有使用自签名证书的内部系统或测试环境。本节主要分享如何使用Jsoup来访问第三方接口,以及如何解决访问Https协议的问题。

1、使用Jsoup来访问第三方接口

        首先我们来看一下正常情况下要使用Jsoup来访问第三方接口的正确方式,核心代码如下:

package com.yelang.project.jsoupcase;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
public class RawDataHomePage {public static void main(String[] args) {try {Connection.Response response = Jsoup.connect("http://data.stats.gov.cn/easyquery.htm?m=QueryData&dbcode=fsyd&rowcode=zb&colcode=sj&wds=%5B%7B%22wdcode%22%3A%22reg%22%2C%22valuecode%22%3A%22110000%22%7D%5D&dfwds=%5B%7B%22wdcode%22%3A%22zb%22%2C%22valuecode%22%3A%22A030102%22%7D%5D&k1=1692174996701&h=1").userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.54").ignoreContentType(true).execute();String body = response.body();System.out.println(body);} catch (IOException e) {e.printStackTrace();}}
}

        以上代码编写完成后,运行程序后,遇到以上问题怎么解决呢?如果大家有验证证书的话,可以直接在Jsoup中带上验证证书即可,这样也不会出现问题。如果我们什么都没有,却因为项目需要,也需要接入数据,那么如何解决呢?

2、Jsoup解决访问Https问题的方法

解决问题的关键就是,我们在程序访问时使用忽略的方式来避免Https验证。设置忽略的核心代码如下:

package com.yelang.project.jsoupcase;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
public class RawDataHomePage {public static void main(String[] args) {// 创建自定义 TrustManagerTrustManager[] trustManagers = new TrustManager[]{new TrustAllCertManager()};// 获取默认的 SSLContextSSLContext sslContext = null;try {sslContext = SSLContext.getInstance("SSL");sslContext.init(null, trustManagers, new java.security.SecureRandom());} catch (NoSuchAlgorithmException | KeyManagementException e) {e.printStackTrace();}// 应用自定义的 SSLContextHttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());try {Connection.Response response = Jsoup.connect("http://data.stats.gov.cn/easyquery.htm?m=QueryData&dbcode=fsyd&rowcode=zb&colcode=sj&wds=%5B%7B%22wdcode%22%3A%22reg%22%2C%22valuecode%22%3A%22110000%22%7D%5D&dfwds=%5B%7B%22wdcode%22%3A%22zb%22%2C%22valuecode%22%3A%22A030102%22%7D%5D&k1=1692174996701&h=1").userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.54").ignoreContentType(true).execute();String body = response.body();System.out.println(body);} catch (IOException e) {e.printStackTrace();}}
}

        这里通过创建自定义 TrustManager的方式类来进行设置,避免输入证书。设置完毕后,再次运行数据,可以在控制台看到后台服务有接口返回了。

三、UniHttp的实现与问题解决

       除了可以使用Jsoup的方式来进行接口集成,我们也可以试试 UniHttp(作为现代HTTP客户端的封装)来进行企业级微服务调用。看看使用UniHttp会遇到什么样的问题?本节我们将详细介绍如何使用UniHttp来进行第三方接口定义与调用以及如何解决Https的访问问题。

1、UniHttp定义第三方接口

首先为了演示方便,我们将前面的访问接口使用UniHttp来进行创建引用定义。定义的核心代码如下:

package com.yelang.project.thridinterface.freeapi;
import com.burukeyou.uniapi.http.annotation.HttpApi;
import com.burukeyou.uniapi.http.annotation.SslCfg;
import com.burukeyou.uniapi.http.annotation.param.QueryPar;
import com.burukeyou.uniapi.http.annotation.request.GetHttpInterface;
import com.burukeyou.uniapi.http.core.response.HttpResponse;
@HttpApi(url = "https://data.stats.gov.cn")
public interface GovStatService {@GetHttpInterface("/easyquery.htm?m=QueryData&dbcode=fsyd&rowcode=zb&colcode=sj&wds=%5B%7B%22wdcode%22%3A%22reg%22%2C%22valuecode%22%3A%22110000%22%7D%5D&dfwds=%5B%7B%22wdcode%22%3A%22zb%22%2C%22valuecode%22%3A%22A030102%22%7D%5D&k1=1692174996701&h=1")public HttpResponse<String> easyqueryOne();@GetHttpInterface("/easyquery.htm")public HttpResponse<String> easyQueryTwo(@QueryPar("m") String m, @QueryPar("dbcode") String dbcode,@QueryPar("rowcode") String rowcode, @QueryPar("colcode") String colcode, @QueryPar("wds") String wds,@QueryPar("dfwds") String dfwds, @QueryPar("k1") String k1, @QueryPar("h") String h);
}

        以上这两个方法都是一个方法,不一样的区别就是第一个查询接口的查询参数是不能修改的,第二个查询接口的参数是可以动态传入。

2、UniHttp调用接口实战

然后在Junit组件中来调用这个接口,并且调用官网的服务接口,核心代码如下:

package com.yelang.project.unihttp;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.burukeyou.uniapi.http.core.response.HttpResponse;
import com.yelang.project.thridinterface.freeapi.GovStatService;
@SpringBootTest
@RunWith(SpringRunner.class)
public class GovStatServiceCase {@Autowiredprivate GovStatService govStatService;@Testpublic void testOne() {try {HttpResponse<String> result = govStatService.easyqueryOne();System.out.println(result.getBodyResult());} catch (Exception e) {e.printStackTrace();}}@Testpublic void testTwo() {String m = "QueryData";String dbcode = "fsyd";	String rowcode = "zb";String colcode = "sj";String wds = "[{\"wdcode\":\"reg\",\"valuecode\":\"110000\"}]";String dfwds = "[{\"wdcode\":\"zb\",\"valuecode\":\"A030102\"}]";String k1 = "1692174996701";String h = "1";HttpResponse<String> result = govStatService.easyQueryTwo(m, dbcode, rowcode, colcode, wds, dfwds, k1, h);System.out.println(result);System.out.println(result.getBodyResult());}
}

        运行之后,你会发现,两个方法都报错了,都是跟Jsoup差不多的错误信息。

        在异常的信息可以看到最原始的异常如下:

3、UniHttp解决Https的访问问题

        首先来看看在UniHttp中有没有相关的技术点,在其官方文档上可以看到以下的章节说明:

        可以看到,在UniHttp中,对于https的访问是有控制的,接着往下看,在UniHttp中,其支持单向和双向认证。单向认证支持直接配置 证书certificate 或者 keysotre。 并且支持配置成文件路径、或者文件base64内容 , 会动态识别和取值。而单向认证又有三种不同的设置方法。

        配法1、使用证书文件配置信任证书:

// 方式1) 配置证书文件的内容
@SslCfg(trustCertificate = "信任证书文件的base64内容")// 方式2) 配置证书文件的路径
@SslCfg(trustCertificate = "classpath:ssl/server.crt")// 方式3)配置证书的环境变量,从环境变量取值. 
// 请确保配置了该环境变量为证书文路径件或者证书内容
@SslCfg(trustCertificate = "${channel.mtuan.ssl.cert}")

        配法2、没有证书文件,也可以使用keystore文件配置信任证书.

@SslCfg(// 配置keystore文件路径 (当前同上也可配置文件base64内容、或者环境变量)trustStore = "classpath:ssl/server01.p12",// keyspre文件类型。 在jdk8不配默认是 jks . 而在jdk11默认是 PKCS12trustStoreType="PKCS12",// keystore文件密码trustStorePassword = "文件密码"
)

        配法3、如果你啥证书也没有,也可以配置直接关闭SSL验证。

@SslCfg(// 关闭信任证书校验、会信任所有证书  closeCertificateTrustVerify=true,// 关闭域名校验,信任所有域名。  否则会校验证书SAN或者CNcloseHostnameVerify=true
)

        双向认证相比单向认证需要多提供一个keystore文件并且里面包含客户端自己私钥和公钥证书, 所以信任证书的配置此处不再描述,同单向认证一致。

        配法1、分别配置 证书和私钥

@SslCfg(// 客户端的公钥证书    (当前同上也可配置文件base64内容、或者环境变量)certificate = "classpath:ssl/client.crt",// 客户端的私钥文件      (当前同上也可配置文件base64内容、或者环境变量)certificatePrivateKey = "classpath:ssl/client.key"
)

        配法2、如果已经放到keysotre文件里也可以直接配置keystore文件

@SslCfg(// keystore文件路径    (当前同上也可配置文件base64内容、或者环境变量)keyStore = "classpath:ssl2/ca_client.pkcs12",// keyspre文件类型。 在jdk8不配默认是 jks . 而在jdk11默认是 PKCS12keyStoreType = "PKCS12",// keystore文件密码keyStorePassword = "文件密码",// 使用的key条目, 如果不配置默认使用第一个密钥条目。keyAlias = "key条目别名",// key条目密码keyPassword = "key条目密码"
)

        这里我们使用单向配置中的最后一条,首先配置关闭验证吧,核心代码如下:

package com.yelang.project.thridinterface.freeapi;
import com.burukeyou.uniapi.http.annotation.HttpApi;
import com.burukeyou.uniapi.http.annotation.SslCfg;
import com.burukeyou.uniapi.http.annotation.param.QueryPar;
import com.burukeyou.uniapi.http.annotation.request.GetHttpInterface;
import com.burukeyou.uniapi.http.core.response.HttpResponse;
@HttpApi(url = "https://data.stats.gov.cn")
public interface GovStatService {@GetHttpInterface("/easyquery.htm?m=QueryData&dbcode=fsyd&rowcode=zb&colcode=sj&wds=%5B%7B%22wdcode%22%3A%22reg%22%2C%22valuecode%22%3A%22110000%22%7D%5D&dfwds=%5B%7B%22wdcode%22%3A%22zb%22%2C%22valuecode%22%3A%22A030102%22%7D%5D&k1=1692174996701&h=1")@SslCfg( // 关闭信任证书校验、会信任所有证书closeCertificateTrustVerify = true,// 关闭域名校验,信任所有域名。 否则会校验证书SAN或者CNcloseHostnameVerify = true)public HttpResponse<String> easyqueryOne();@GetHttpInterface("/easyquery.htm")@SslCfg( // 关闭信任证书校验、会信任所有证书closeCertificateTrustVerify = true,// 关闭域名校验,信任所有域名。 否则会校验证书SAN或者CNcloseHostnameVerify = true)public HttpResponse<String> easyQueryTwo(@QueryPar("m") String m, @QueryPar("dbcode") String dbcode,@QueryPar("rowcode") String rowcode, @QueryPar("colcode") String colcode, @QueryPar("wds") String wds,@QueryPar("dfwds") String dfwds, @QueryPar("k1") String k1, @QueryPar("h") String h);
}

        在此运行之前的测试程序,则可以正常获取数据,并进行输出,如下图所示:

四、总结

        以上就是本文的主要内容,本文详细的介绍了在开发中遇到的需要获取Http是网站的服务的场景,以及在Java中使用Jsoup和UniHttp来分别进行接口服务集成的具体实现与遇到的问题。通过实例的讲解,不仅让大家明白如何在Java中如何具体实现,也同时使用不同的方法说明Jsoup和Unihttp如何解决Https网站接口的问题,希望通过本文对开发者有所帮助。行文仓促,定有不足之处,欢迎各位朋友在评论区批评指正,不胜感激。

http://www.dtcms.com/a/581880.html

相关文章:

  • Notepad++ 7.8.4 安装步骤详解(附 npp.7.8.4.Installer 安装教程)
  • 柳市网站建设哪家好wordpress七牛图片插件
  • 用 MCP 重构 RAG 智能体:3 步解决数据安全与多工具协同难题
  • QOS服务质量
  • 如何为视频进行外语配音:分步指南
  • 建设部网站投诉核查企业名单网页和网站的概念
  • kanass零基础学习,项目负责人如何使用kanass驾驭项目
  • redis实战篇day04
  • 罗湖网站公司服务器网站管理助手
  • 八股训练营第 10 天 | 进程和线程之间有什么区别?并行和并发有什么区别?解释一下用户态和核心态,什么场景下,会发生内核态和用户态的切换?
  • AIDAv2:重新定义DeFi的AI驱动金融基础设施
  • SAP PP未清生产订单关闭物料退料、新工单发料批量处理
  • 下载软件的网站哪个好用哪个网站做首页好
  • 【零基础一站式指南】Conda 学习环境准备与 Jupyter/PyCharm 完全配置
  • 滨州制作网站深圳优化公司
  • mysql第四次做业
  • clusterProfile包用于宏基因组学富集分析
  • 湖北网站开发培训写一篇软文多少钱
  • python+django/flask基于协同过滤算法的理财产品推荐系统
  • h5个网站的区别某颜值女主播低俗内容流出视频
  • 做网站600房产管理局官网查询入口
  • 品牌网站建设要选磐石网络安阳县
  • 深圳企业网站托管长春专业网站建设模板
  • 陕煤建设集团韩城分公司网站免费的效果图设计软件
  • Web APIs 入门到实战(day5):解决数据丢失痛点——JS 本地存储 + BOM 操作实战案例(实现数据持久化学生就业表)
  • 本地前端独立开发(后端未启动)登录解决方案
  • HTML<output>标签
  • 淘宝客建网站要钱的吗房产信息网站模板
  • 山东省住房建设厅网站首页宝宝身上出现很多小红疹怎么办
  • 环境配置|GPUStack——为大模型而生的开源GPU集群管理器