升级Spring Boot3.2+JDK17后本地单元测试出现SunCertPathBuilderException
问题分析与解决方案
核心问题出在Spring Boot 3.2(基于Spring Framework 6.x)对RestTemplate的默认SSL上下文构建逻辑做了调整,结合JDK 17的SSL验证特性,导致本地环境无法识别公司内部URL的CA证书;而CI/CD环境因提前配置了信任库或打包时嵌入了证书,所以不受影响。另外Spring Boot 2.7+JDK17的组合没问题,是因为旧版本RestTemplate默认沿用了系统信任库的兼容逻辑。
排查步骤
- 确认本地JDK的默认信任库(
$JAVA_HOME/lib/security/cacerts)是否包含公司内部服务的CA证书,CI/CD环境通常会提前导入这类证书或使用自定义信任库。 - 检查单元测试中RestTemplate的实例来源:是Spring自动配置的实例,还是自定义配置的?Spring Boot 3.x的
RestTemplateAutoConfiguration对默认客户端的选择(比如切换到HttpClient 5)可能导致SSL加载逻辑变化。 - 验证本地测试的JDK版本:确保运行测试的JDK和CI环境一致(比如都是OpenJDK 17),避免Oracle JDK和OpenJDK的信任库差异。
解决方法
方法1:手动给RestTemplate配置自定义信任管理器
在测试配置类中创建包含公司CA证书的RestTemplate实例,确保单元测试使用该实例:
@Configuration @TestConfiguration public class TestRestTemplateConfig { @Bean public RestTemplate testRestTemplate() throws Exception { // 加载放在src/test/resources下的公司内部CA证书 InputStream certStream = getClass().getResourceAsStream("/company-ca.crt"); CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); X509Certificate caCert = (X509Certificate) certFactory.generateCertificate(certStream); certStream.close(); // 初始化信任库并添加证书 KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustStore.load(null); trustStore.setCertificateEntry("internal-ca", caCert); // 构建信任管理器 TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmFactory.init(trustStore); // 创建SSL上下文 SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, tmFactory.getTrustManagers(), new SecureRandom()); // 配置HttpClient使用自定义SSL上下文 HttpClient httpClient = HttpClientBuilder.create() .setSSLContext(sslContext) .build(); HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); return new RestTemplate(requestFactory); } }
之后在单元测试中注入这个testRestTemplate即可。
方法2:导入证书到本地JDK信任库
使用keytool命令将公司CA证书导入本地JDK的默认信任库:
keytool -import -trustcacerts -alias internal-ca -file /path/to/company-ca.crt -keystore $JAVA_HOME/lib/security/cacerts
默认密码为changeit,导入后重启单元测试即可。注意要确保测试使用的JDK是你导入证书的那个版本。
方法3:通过系统属性指定自定义信任库
如果不想修改默认JDK信任库,可以在单元测试启动时添加VM参数,指定自定义信任库路径:
-Djavax.net.ssl.trustStore=/path/to/your/custom-truststore.jks -Djavax.net.ssl.trustStorePassword=your-password
可以在IDE的测试运行配置中添加这些参数,或者在Maven/Gradle的测试配置中指定。
内容的提问来源于stack exchange,提问作者tys
相关产品推荐
相关产品推荐

