PAYATHON 2026

Java 与 LAMP 之间出现 SSL error,如何解决?

支付小周

结论

页面并非不存在。问题在于 payment gateway 的 Java 客户端无法为网站证书建立受信任的证书链。浏览器能正常打开页面,SSL Labs 评分正常,也不代表对方 JVM 使用的信任库能够验证这张证书。

首先应检查网站服务器是否发送了完整的中间证书:

  • 如果服务器没有发送完整证书链,应由网站方修复。
  • 如果证书链完整,但对方使用了过旧或自定义的 Java truststore,则需要 payment gateway 更新 JVM、CA 信任库或连接配置。
  • 不要通过关闭证书验证来绕过问题,支付流程尤其不能这样处理。

错误的含义

异常中的关键信息是:

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

进行 TLS 握手时,Java 需要从网站证书逐级找到受信任的根证书。证书路径大致如下:

网站证书 → 中间 CA 证书 → 根 CA 证书

如果 Java 无法构建这条路径,就会抛出 SSLHandshakeException。常见原因有:

  • Web 服务器没有发送中间证书,或者证书链的顺序或内容有误。
  • 对方 JVM 的 CA 信任库过旧,不信任当前根证书。
  • 对方使用了单独的自定义 truststore,而不是操作系统或浏览器的证书库。
  • 证书由私有 CA 或内部 CA 签发,但相应 CA 尚未导入对方的 truststore。
  • 对方通过代理、防火墙或 TLS 检查设备访问网站,实际收到的是另一张证书。
  • 同一域名指向多个节点,但各节点的证书链配置不一致。
  • 对方没有正确发送 SNI,因此收到了虚拟主机的默认证书。

这条错误指向证书信任路径,不能直接说明问题出在 TLS 版本、加密套件或页面 URL。

先检查服务器发送的证书链

在可以访问该域名的环境中执行:

openssl s_client \
  -connect www.example.com:443 \
  -servername www.example.com \
  -showcerts \
  -verify_return_error </dev/null

重点检查最后的验证结果:

Verify return code: 0 (ok)

还要确认输出中包含网站证书和必要的中间 CA 证书。服务器通常不需要发送根证书,但必须提供客户端构建信任路径所需的中间证书。

也可以单独测试 IPv4、IPv6 或具体节点,以排除负载均衡后端配置不一致的问题:

openssl s_client \
  -connect 203.0.113.10:443 \
  -servername www.example.com \
  -showcerts \
  -verify_return_error </dev/null

如果域名经过 CDN、反向代理或负载均衡,应检查所有对外节点,不能只测试源站。

修复 Apache 的证书链配置

Apache 应使用包含网站证书和中间证书的完整链文件。以下是常见的 PEM 部署方式:

<VirtualHost *:443>
    ServerName www.example.com

    SSLEngine on
    SSLCertificateFile /etc/ssl/example/fullchain.pem
    SSLCertificateKeyFile /etc/ssl/example/privkey.pem
</VirtualHost>

fullchain.pem 通常按以下顺序组成:

-----BEGIN CERTIFICATE-----
网站证书
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
中间 CA 证书
-----END CERTIFICATE-----

具体文件名和 Apache 配置方式取决于证书签发机构、Apache 版本和部署环境。修改后先检查配置:

apachectl configtest

确认无误后重新加载服务:

systemctl reload apache2

部分发行版使用的服务名称是 httpd:

systemctl reload httpd

修复后应再次通过 openssl s_client 验证,不能只看浏览器能否正常访问。

为什么浏览器能打开,Java 却失败

浏览器和 Java 使用的信任机制可能完全不同:

  • 浏览器可能使用操作系统证书库,Java 通常使用自己的 cacerts 或自定义 truststore。
  • 浏览器可能缓存了中间证书。
  • 某些浏览器可以通过证书中的 AIA 地址下载缺少的中间证书,Java 客户端未必会这样处理。
  • 浏览器和在线测试工具可能已经信任较新的根 CA,而旧 JVM 的信任库尚未收录。
  • 浏览器会正确发送 SNI,某些旧客户端或配置错误的 Java 程序则可能不会。

所以,浏览器正常只能说明浏览器完成了证书验证,不能据此排除服务器证书链或 Java truststore 的问题。

如果证书链完整,需要 payment gateway 检查什么

如果从多个网络和节点测试后,都确认服务器发送了正确的完整证书链,应向 payment gateway 提供以下信息:

  • 错误发生的准确时间和请求 URL。
  • 网站证书的 SHA-256 指纹。
  • 完整的 openssl s_client 输出。
  • 证书链中各证书的 Subject、Issuer 和有效期。
  • 域名对应的 IPv4、IPv6 和 CDN 节点信息。
  • 近期是否更换过证书、CA、CDN 或中间证书。

对方需要检查:

  1. Java 运行时版本及其 CA 信任库是否过旧。
  2. 应用是否通过 javax.net.ssl.trustStore 指定了自定义 truststore。
  3. truststore 中是否缺少所需的根 CA 或私有 CA。
  4. 应用实际连接的 IP、代理和证书是否与公开测试结果一致。
  5. 应用是否正确发送 SNI,以及是否使用了错误的主机名。
  6. 多台网关服务器的 JVM 和 truststore 配置是否一致。

如需确认 Java 实际使用的 truststore,可让对方检查启动参数:

java -XshowSettings:properties -version

以及以下系统属性:

javax.net.ssl.trustStore
javax.net.ssl.trustStoreType
javax.net.ssl.trustStorePassword

还可以临时开启 Java TLS 调试日志:

java -Djavax.net.debug=ssl,handshake,certpath -jar application.jar

调试日志可能包含域名、证书和连接信息,不应直接公开。

必要时导入 CA 证书

如果网站使用私有 CA,或者对方确实使用受控的自定义 truststore,应由 truststore 的维护方导入相应的 CA 证书:

keytool -importcert \
  -alias example-intermediate-ca \
  -file intermediate-ca.pem \
  -keystore gateway-truststore.p12 \
  -storetype PKCS12

导入前必须核对证书来源和 SHA-256 指纹:

openssl x509 \
  -in intermediate-ca.pem \
  -noout \
  -subject \
  -issuer \
  -fingerprint \
  -sha256

应优先导入可信的 CA 证书,不要长期把单张网站叶证书加入 truststore。网站证书更新后,直接信任叶证书的配置很容易再次失效。

也不要在未经评估的情况下直接修改全局 cacerts。通常更稳妥的做法是维护应用专用的 truststore,并清楚记录其中的信任项。

不建议采用的绕过方式

不要使用信任所有证书的 TrustManager,不要关闭主机名验证,也不要把支付回调改为未加密的 HTTP。以下做法会使中间人攻击成为可能:

// 不要在生产环境中使用信任所有证书的 TrustManager。

关闭验证也许能暂时让握手通过,但 HTTPS 将无法验证服务器身份。在支付场景中,这种处理方式不能作为修复方案。

责任如何判断

责任归属要根据证书链测试结果判断:

  • 服务器没有发送完整的中间证书,应由网站、CDN 或负载均衡的配置方修复。
  • 只有 payment gateway 失败,而标准客户端能够完整验证服务器证书链,问题大概率在 gateway 的 JVM、truststore、代理或 SNI 配置。
  • 如果使用私有 CA,双方需要协调信任证书的安全分发和更新。
  • 如果只有部分请求失败,应优先检查多节点配置、IPv4 和 IPv6、代理路径以及网关集群之间的差异。

处理时,先验证所有对外节点是否发送了完整证书链,再把证书链、指纹和复现时间交给 payment gateway。确认服务器端配置无误后,才能较有把握地判断问题来自对方的 Java 环境。

备注:内容仅供参考。