Spring升级至SpringBoot后触发sun.security.validator.ValidatorException证书错误
我之前在项目从Spring升级到Spring Boot时,也碰到过几乎一模一样的问题。结合你给出的报错信息来看,这明显是Spring Boot的默认SSL配置和原有自定义Keystore的适配逻辑不兼容导致的——报错里的sun.security.validator.ValidatorException: No trusted certificate found,说明你的SOAP客户端在调用接口时,找不到可信任的服务端证书,而Spring Boot和传统Spring在SSL配置的加载优先级、默认逻辑上有不小差异。
下面是我当时解决这个问题的几个关键步骤,你可以逐一排查:
1. 调整Spring Boot的SSL配置方式
传统Spring可能是通过XML或自定义Bean配置Keystore,但Spring Boot更推荐通过配置文件直接声明。如果你的项目用的是application.yml/properties,直接添加以下配置(根据实际文件路径和密码修改):
# 服务端SSL配置(如果你的项目同时作为服务端对外提供接口) server: ssl: key-store: classpath:your-custom-keystore.jks key-store-password: your-keystore-pass key-store-type: JKS # 若使用单独的信任库,补充以下配置 trust-store: classpath:your-truststore.jks trust-store-password: your-trust-pass # SOAP客户端专用SSL配置(针对CXF客户端) spring: cxf: client: your-soap-client-id: ssl: trust-store: classpath:your-truststore.jks trust-store-password: your-trust-pass
2. 给CXF SOAP客户端手动绑定SSL上下文
你的报错来自JaxWsClientProxy,说明是CXF客户端发起请求时出了问题。有时候Spring Boot的自动配置不会自动把自定义Keystore绑定到CXF客户端,这时候需要手动构建SSLContext并添加到CXF的拦截器中:
import org.apache.cxf.configuration.jsse.TLSClientParameters; import org.apache.cxf.interceptor.ClientInterceptor; import org.apache.cxf.transport.http.SSLOutInterceptor; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Component; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContextBuilder; import java.io.File; @Component public class CxfSslConfig { public ClientInterceptor createSslInterceptor() throws Exception { // 加载自定义信任库和密钥库文件 File trustStoreFile = new ClassPathResource("your-truststore.jks").getFile(); File keyStoreFile = new ClassPathResource("your-custom-keystore.jks").getFile(); // 构建SSL上下文 SSLContext sslContext = SSLContextBuilder.create() .loadTrustMaterial(trustStoreFile, "your-trust-pass".toCharArray()) .loadKeyMaterial(keyStoreFile, "your-keystore-pass".toCharArray(), "your-keystore-pass".toCharArray()) .build(); // 配置CXF的TLS参数 TLSClientParameters tlsParams = new TLSClientParameters(); tlsParams.setSSLSocketFactory(sslContext.getSocketFactory()); // 创建并返回SSL拦截器 SSLOutInterceptor sslOutInterceptor = new SSLOutInterceptor(); sslOutInterceptor.setTlsClientParameters(tlsParams); return sslOutInterceptor; } }
接着在你的SOAP客户端Bean中添加这个拦截器:
import org.apache.cxf.frontend.ClientProxy; import org.apache.cxf.interceptor.ClientInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; @Component public class SoapClientConfig { @Autowired private CxfSslConfig cxfSslConfig; @Bean public YourSoapServiceClient yourSoapServiceClient() throws Exception { YourSoapServiceClient client = new YourSoapServiceClient(); // 获取CXF代理对象 org.apache.cxf.endpoint.Client proxy = ClientProxy.getClient(client); // 添加SSL拦截器 ClientInterceptor sslInterceptor = cxfSslConfig.createSslInterceptor(); proxy.getOutInterceptors().add(sslInterceptor); return client; } }
3. 验证自定义Keystore/Truststore的有效性
有时候问题出在证书本身——比如你没有把服务端的证书正确导入到信任库中。可以用keytool命令检查信任库中的证书列表:
keytool -list -v -keystore your-truststore.jks -storepass your-trust-pass
如果目标SOAP服务端的证书不在列表里,用下面的命令导入:
keytool -import -alias server-cert -file server-cert.crt -keystore your-truststore.jks -storepass your-trust-pass
4. 排查Spring Boot自动配置的冲突
Spring Boot会自动加载默认的SSL配置(比如JRE自带的cacerts信任库),如果你的自定义配置没有覆盖默认值,就会导致客户端使用默认信任库而找不到你的自定义证书。可以通过以下方式确认:
- 在启动日志中搜索
SSL相关内容,查看Spring Boot实际加载的是哪个Keystore/Truststore - 如果确实是自动配置覆盖了自定义配置,可以通过
@SpringBootApplication(exclude = {SslAutoConfiguration.class})禁用自动配置(不推荐,除非万不得已)
内容的提问来源于stack exchange,提问作者Daniel Hawes

