Java客户端访问HTTPS失败:unable to find valid certification path to requested target

本文档深入剖析了Java客户端因服务器未提供完整证书链而导致“unable to find valid certification path”的HTTPS访问失败问题,并提供使用openssl排查及在服务端配置完整证书链的实用解决方案。

问题现象

Java应用程序通过HTTPS访问服务端点时,连接失败并抛出javax.net.ssl.SSLHandshakeException异常,核心错误信息如下:

javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

该问题通常伴随以下现象:

  • 浏览器访问正常:使用主流浏览器(如Chrome、Firefox)访问同一HTTPS地址,连接成功且显示安全锁标志。

  • 部分客户端失败:Java应用程序、curlwget等非浏览器客户端访问时,均报告TLS握手失败或证书验证错误。

问题原因

SSL/TLS协议依赖一个从服务器证书到客户端信任的根证书的完整“信任链”。此错误的根本原因是Java客户端无法构建出这条完整的信任链。

  • 原因一:服务器未提供完整的证书链

    服务器在TLS握手时,仅发送了服务器证书(域名证书),但遗漏了必要的中间证书。大多数非浏览器客户端(包括Java)默认不具备从网络自动下载缺失证书的能力,导致验证失败。而现代浏览器具备AIA(Authority Information Access) chasing功能,可自动下载缺失的中间证书以补全证书链,因此访问正常。

  • 原因二:客户端JDK信任库过旧

    服务器已正确配置并发送了完整证书链,但证书链的根证书(或交叉签名的根证书)未被客户端的JDK信任。通常发生在客户端JDK版本过旧,其内置的信任库($JAVA_HOME/jre/lib/security/cacerts)不包含新版根证书(例如,DigiCertG1根切换到G2根后,旧版JDK可能不信任G2根),详情可参考关于DigiCert根替换公告

解决方案

排查此问题应遵循“先服务端,后客户端”的顺序。首先使用openssl命令诊断服务器配置,确认证书链是否完整。若服务端配置无误,再检查客户端环境。

步骤一:检查服务器证书链配置

此步骤用于确认服务器在TLS握手时是否发送了完整的证书链。

  1. 在任意一台已安装OpenSSL的设备上,执行以下命令。将your.domain.com:443替换为实际的服务端点地址和端口。

    # 连接服务器并显示其提供的证书链
    openssl s_client -connect your.domain.com:443 -showcerts
  2. 分析命令输出中的Certificate chain部分和Verify return code

    • 问题场景(证书链不完整)

      输出中仅有一个以depth=0开头的证书,且末尾的验证返回码非0,通常为Verify return code: 20 (unable to get local issuer certificate)。这明确表示服务器未提供中间证书。

      Certificate chain
       0 s:/CN=your.domain.com
         i:/C=US/O=DigiCert Inc/CN=DigiCert TLS RSA SHA256 2020 CA1
      ---
      Server certificate
      -----BEGIN CERTIFICATE-----
      (服务器证书内容)
      -----END CERTIFICATE-----
      ...
      Verify return code: 20 (unable to get local issuer certificate)
    • 正常场景(证书链完整)

      输出中包含多个证书,形成从depth=0(服务器证书)到depth=1(中间证书)的有序链条。验证返回码为Verify return code: 0 (ok)

      Certificate chain
       0 s:/CN=your.domain.com
         i:/C=US/O=DigiCert Inc/CN=DigiCert TLS RSA SHA256 2020 CA1
       1 s:/C=US/O=DigiCert Inc/CN=DigiCert TLS RSA SHA256 2020 CA1
         i:/C=US/O=DigiCert Inc/CN=DigiCert Global Root CA
      ---
      ...
      Verify return code: 0 (ok)

步骤二:修复问题

根据步骤一的诊断结果,执行对应的修复操作。

场景一:修复不完整的服务器证书链

openssl诊断结果为证书链不完整,需在服务器端(如Nginx、Apache、Tomcat、负载均衡SLB等)重新配置证书。

  1. 从证书颁发机构(CA)获取包含服务器证书和所有中间证书的证书包文件。该文件通常命名为fullchain.pemchain.pem

  2. 确保证书包文件内容遵循以下顺序:服务器证书在前,中间证书在后。

    -----BEGIN CERTIFICATE-----
    (您的服务器证书内容)
    -----END CERTIFICATE-----
    -----BEGIN CERTIFICATE-----
    (中间证书1的内容)
    -----END CERTIFICATE-----
    -----BEGIN CERTIFICATE-----
    (中间证书2的内容,如果存在)
    -----END CERTIFICATE-----
  3. 参考您的Web服务器或网关的官方文档,将其SSL证书配置指向此完整的证书包文件,然后重启服务使配置生效。

场景二:处理客户端JDK信任库问题

openssl诊断结果显示服务器证书链完整,但Java客户端依然报错,则问题很可能源于客户端JDK版本过旧。

  • 推荐方案:升级JDK 将客户端应用的JDK升级至最新的长期支持(LTS)版本(如JDK 8、11、17的最新更新版)。新版JDK包含了最新的根证书库,能解决因CA根证书更替导致的问题,同时也能获得重要的安全修复和性能提升。

  • 临时方案:手动导入根证书至信任库 若无法立即升级JDK,可手动将缺失的根证书导入到当前JDKcacerts信任库中。

    1. 获取缺失的根证书文件,可参考下载根证书

    2. 执行以下keytool命令将其导入。默认密码为changeit

      # 将<path-to-root-ca.crt>替换为根证书文件路径
      # 将$JAVA_HOME替换为您的Java安装目录
      keytool -import -alias <give-a-unique-alias> -keystore $JAVA_HOME/jre/lib/security/cacerts -file <path-to-root-ca.crt> -storepass changeit

后续建议

  • 配置优化:将证书更新流程标准化。在每次更新证书时,都必须验证并部署完整的证书链文件,而非仅部署域名证书文件。

  • 监控告警购买并开启公网域名监控,定期探测HTTPS服务的证书状态,避免证书过期导致站点不可用。