SSL证书信任链接错误排查指南

0 阅读4分钟

问题背景

1/Oerror on POSTrequestfor "xxx.com/resource/Ro…: PKIX path building failed:sun.security.provider.certpath.SunCertPathBuilderException: unable to find validcertification path to requested target; nested exception is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unableto find valid certification path to requested target

问题解析

image.png 这个错误本质上是:

Java 在发起 HTTPS 请求时,无法验证目标网站的 SSL 证书链。

你这个报错里最关键的一句是:

PKIX path building failed
unable to find valid certification path to requested target

说明:

  • Java 客户端不信任对方证书
  • 或者对方证书链不完整
  • 或者 JVM cacerts 里缺少对应根证书
  • 或者用了自签名证书
  • 或者网关/代理替换了证书

你这个请求目标是:

https://d.ceekeeapi.com/huilianyi/resource/getHotelRoomRateList

属于典型第三方接口 SSL 握手失败。


一、先明确:这不是代码业务错误

很多人会误判:

  • 不是 JSON 错
  • 不是 HTTP 400
  • 不是参数问题
  • 不是 Spring RestTemplate 问题
  • 不是 POST 请求问题

而是:

TLS/SSL 证书信任链问题

发生在:

HTTP 请求真正发出去之前

即:

SSL Handshake 阶段

二、错误拆解(非常关键)

你的异常链:

SSLHandshakeException
  -> ValidatorException
      -> SunCertPathBuilderException

逐层含义:


1. SSLHandshakeException

说明:

HTTPS 握手失败

Java 与服务器建立 TLS 通信失败。


2. ValidatorException

说明:

证书校验失败

Java 不认可服务器返回的证书。


3. SunCertPathBuilderException

这是核心。

意思:

Java 无法构建完整可信证书链

即:

服务器证书
   ↓
中间证书
   ↓
根证书

其中某一环缺失或不可信。


三、最常见的 7 个原因


原因1:服务器没配完整证书链(最常见)

很多 Nginx / 网关:

只配置了:

ssl_certificate xxx.crt

但:

缺少 intermediate certificate

于是:

浏览器能访问(因为浏览器自动补全)

但:

Java 不行。

这是 Java 最常见 SSL 坑。


原因2:对方用了自签名证书

比如:

test 环境
内网环境
UAT 环境

证书不是正规 CA 签发。

Java 默认不信。


原因3:JDK 太老

比如:

JDK 1.8.0_121

里面根证书库太旧。

现在很多证书:

  • Let's Encrypt
  • DigiCert
  • GlobalSign

老 JDK 不认。


原因4:公司代理替换 HTTPS 证书

比如:

  • 蓝盾
  • ZScaler
  • 企业网关
  • 抓包代理

会:

中间人代理 HTTPS

Java 不认识代理证书。


原因5:容器里的 cacerts 缺失

Docker 镜像里:

ca-certificates 不完整

尤其:

  • alpine
  • 精简 JRE
  • distroless

很容易出问题。


原因6:目标网站证书过期

虽然少见,但必须检查。


原因7:SNI/TLS版本问题

极少见。

比如:

  • TLS1.0 被禁
  • 老 JDK 不支持 TLS1.2
  • SNI 域名识别失败

四、最快排查方案(生产级)


第一步:浏览器打开目标地址

直接访问:

https://d.ceekeeapi.com

看:

  • 是否证书告警
  • 是否证书过期

第二步:openssl 查看完整证书链(最关键)

Linux 执行:

openssl s_client -showcerts -connect d.ceekeeapi.com:443

重点看:

Certificate chain

如果只有一段:

0 s:

没有:

1 s:
2 s:

基本就是:

中间证书没配

第三步:检查 JVM 版本

执行:

java -version

如果太老:

建议升级:

JDK8u301+

第四步:验证 JVM 是否信任该证书

执行:

keytool -list -cacerts

查看是否存在对应根证书。


五、真正的解决方案(按优先级)


方案1:让对方修复证书链(最佳方案)

这是正确方案。

让接口提供方:

正确配置:

fullchain.pem

而不是:

server.crt

Nginx 示例:

ssl_certificate fullchain.pem;
ssl_certificate_key privkey.pem;

方案2:升级 JDK(非常有效)

很多 SSL 问题:

升级 JDK 直接解决。

建议:

JDK8u301+
JDK11+
JDK17+

方案3:导入证书到 cacerts

适合:

  • 内网系统
  • 自签名证书
  • 临时处理

步骤1:下载证书

浏览器导出:

cer/crt

步骤2:导入 JVM

keytool -import -alias huilianyi \
-file huilianyi.cer \
-keystore $JAVA_HOME/lib/security/cacerts

默认密码:

changeit

方案4:代码里自定义 TrustStore

Spring Boot:

-Djavax.net.ssl.trustStore=truststore.jks
-Djavax.net.ssl.trustStorePassword=123456

六、千万别这么干(危险)

很多文章会教:

TrustAll
IgnoreSSL
NoopHostnameVerifier

即:

跳过 SSL 校验

生产环境非常危险。

等于:

任何中间人证书都信任

支付系统尤其不能这么做。


七、如果这是突然出现的

重点怀疑:


1. 对方换证书了

最常见。

尤其:

Let's Encrypt 自动续期。


2. 服务器迁移

新机器没配 fullchain。


3. JDK 更新导致算法不兼容

比如:

SHA1 被禁

4. Docker 镜像变化

尤其:

FROM alpine

八、你这个问题的高概率结论

从异常特征看:

大概率是:

目标服务证书链不完整

因为:

PKIX path building failed

是典型:

intermediate certificate missing

问题。

尤其第三方 API 非常常见。


九、建议你这样处理(最稳)

优先顺序:

1. openssl 检查证书链
2. 升级 JDK
3. 联系第三方修复 fullchain
4. 临时导入证书

不要一上来:

忽略 SSL 校验

这是很多系统后面留下安全事故的根源。