最近在调试一个 Java 客户端请求 HTTPS 接口的功能时,遇到了一个熟悉的错误:
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
目标地址用的是 Let's Encrypt 签发的免费证书,但 Java 程序就是不买账。下面这张 泳道图 展示了 Java 客户端访问 HTTPS 服务器时的交互流程,重点标出了 SSL 握手过程中出错的位置。流程如下:
这让我重新回忆起了 Java 信任根证书体系的老问题——以及 JDK 10 引入的 JEP 319 是如何试图解决这一问题的。
一、Java客户端访问HTTPS时为何需要根证书?
Java 客户端在访问 HTTPS 服务时,会自动进行 TLS 握手,其中最关键的一步是验证服务器端返回的证书链是否可信。这需要 Java 本地的 信任库(TrustStore) 中包含对应的根证书。
我们可以通过
keytool -list -keystore $JAVA_HOME/lib/security/cacerts -storepass xxx
来查看jdk下载的证书,默认的访问密钥是changeit:
根证书就像是“权威机构签发的身份证模板”。如果不在信任列表中,Java 默认就会拒绝连接,防止中间人攻击或伪造站点。
二、为什么会访问失败?Let's Encrypt 不受信任?
其实从 JDK 10 开始,Java 已经通过 JEP 319 默认信任 Let's Encrypt 所使用的根证书(ISRG Root X1),而在更早版本(如 JDK 8)中,它可能还在依赖已过期的 DST Root CA X3,或干脆没有包含 Let's Encrypt 的根证书。
所以,错误的本质是 Java 的信任库不包含目标站点证书所用的根证书。
三、JEP 319 是什么?
JEP 319: Root Certificates 是 Java 在 JDK 10 中引入的一项重要增强,它的目标非常明确:
将一套广泛使用的、受信任的根证书预装进 Java 的
cacerts
信任库中,简化 HTTPS 通信,提升安全体验。
这项提案由 Oracle 提出,选择了来自 Mozilla NSS 根证书项目 的子集,并通过人工验证确保每一个证书都符合安全和兼容性要求。
📦 改动内容简述:
项目 | 内容 |
---|---|
更新了什么? | 在 lib/security/cacerts 中预装了数十个可信 CA 的根证书 |
包含哪些 CA? | 包括 Let's Encrypt、DigiCert、GlobalSign、Google Trust 等 |
从哪里来的? | 来源于 Mozilla 根计划(NSS) |
有什么好处? | 减少开发者手动导入证书的操作,提高开箱即用性 |
所以在jdk 10+的lib目录中我们可以看到对应security目录:
四、JEP 319 带来的改变
✅ 之前(JDK 8 及更早):
- 默认
cacerts
里只有少量 Oracle 签发或合作 CA 的证书 - 访问 Let's Encrypt、Cloudflare、阿里云 SSL 等都需要手动导入根证书
🚀 现在(JDK 10+):
- Java 内置了常见 CA 根证书
- 大部分公开互联网 HTTPS 服务可直接访问,无需额外配置
- 更加符合现代 Web 应用开发的便利性与安全需求
⚓ 总结
下面用流程图对比一下 JDK 10 之前 与 JDK 10 及以上 版本在访问 HTTPS 地址时,证书验证和连接建立的关键步骤差异。
五、那我该怎么做?
1. 确认你的 JDK 版本
java -version
-
如果你使用的是 JDK 10+,理论上应该不需要额外导入证书。
-
如果是 JDK 8 或更早,建议:
-
升级到更高版本;
-
或手动导入 Let's Encrypt 的根证书(ISRG Root X1):
keytool -import -trustcacerts \ -alias letsencrypt \ -file isrgrootx1.der \ -keystore $JAVA_HOME/lib/security/cacerts \ -storepass changeit
-
2. 使用自定义 TrustStore(非侵入式做法)
java -Djavax.net.ssl.trustStore=/path/to/mytruststore \
-Djavax.net.ssl.trustStorePassword=xxx \
-jar myclient.jar
3. 微服务spring boot的版本支持
如果你使用的是 Let's Encrypt证书,那么以下组合需要注意:
Spring Boot 版本 | JDK 版本 | 是否自动信任 Let's Encrypt |
---|---|---|
2.5.x | JDK 8 | ❌ 不支持,需要手动导入证书 |
2.7.x | JDK 11 | ✅ 支持 JEP 319,自动信任 |
3.0+ | JDK 17+ | ✅ 支持,开箱即用 |
Spring Boot 允许你通过配置使用自定义的证书,但它不负责配置“信任哪些 CA”,这些依赖于:
- 系统 TrustStore:
$JAVA_HOME/lib/security/cacerts
- 或你手动设置的 TrustStore
比如:
# 服务端配置(提供 HTTPS)
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=xxxx
server.ssl.key-store-type=PKCS12
# 客户端配置(通过 RestTemplate 访问 HTTPS)
# 不在 application.properties 中配置,而是通过 Java 代码自定义 SSLContext
最佳实践建议:
场景 | 建议 |
---|---|
你使用的是 JDK 8 | ✅ 手动导入 Let's Encrypt 证书,或升级到 JDK 11+ |
你使用的是 Spring Boot 2.x | ✅ 使用 JDK 11+ 即可自动支持 JEP 319 |
Spring Boot 3.x | 强制要求 JDK 17+,天然支持 JEP 319,开箱即用 |
六、总结
JEP 319 虽然是一项“看不见”的增强,但对 Java 应用连接现代互联网 HTTPS 服务来说意义重大。它简化了证书管理流程,减少了很多因“不受信任证书”导致的连接失败问题。
但与此同时,我们也要意识到,JDK 的选择直接影响到你的应用能否顺利访问 HTTPS 服务。如果你还在使用 JDK 8,建议尽快升级,享受更完整的 TLS 支持和安全能力。
如果你在使用 Java 访问 HTTPS 服务时遇到类似的证书问题,或者对 JEP 319 和 Java 证书管理有更多疑问,欢迎在评论区留言交流! 想了解更多 JDK 新特性,提升开发效率?欢迎关注我的专栏 👉 JDK 新特性专栏!一起来变得更强大吧 🚀