Spring Boot SSL Bundle按需创建及RestTemplate证书重载与fallback设计咨询
Spring Boot SSL Bundle 相关问题解答
已配置的SSL Bundle
spring: ssl: bundle: watch: file: quiet-period: 20s pem: fos-internal-cert-bundle: reload-on-update: ${INTERNAL_CERT_RELOAD:true} keystore: certificate: ${INTERNAL_CERT:classpath:cert/client-certificate.pem} private-key: ${INTERNAL_CERT_PRIVATE_KEY:classpath:cert/private.key}
RestTemplate 绑定SSL Bundle的代码
@Bean("restTemplateWithCertificate") public RestTemplate restTemplateWithBdfCertificate( RestTemplateBuilder builder, RestClientProperties clientProperties, SslBundles sslBundles) { SslBundle sslBundle = sslBundles.getBundle("fos-internal-cert-bundle"); return builder .requestFactory(HttpComponentsClientHttpRequestFactory.class) .additionalInterceptors(new HeaderRequestInterceptor()) .setReadTimeout(Duration.ofSeconds(clientProperties.getReadTimeoutSec())) .setConnectTimeout(Duration.ofSeconds(clientProperties.getConnectionTimeoutSec())) .setSslBundle(sslBundle) .build(); }
问题1:RestTemplate Bean是否会自动加载新证书?
不会。当前的RestTemplate是单例Bean,初始化时就绑定了当时的SslBundle实例。虽然你配置了reload-on-update: true和文件监听,Spring Boot的SslBundle确实会自动重载更新后的证书,但已经初始化完成的RestTemplate不会主动感知到SslBundle的变化——它持有的是初始化时生成的SSL上下文引用,不会自动刷新。
如果想让RestTemplate自动使用新证书,有两种可行方向:
- 不要让RestTemplate持有固定的SSL上下文,每次请求前从
SslBundles中获取最新的fos-internal-cert-bundle实例; - 使用支持动态刷新SSL上下文的请求工厂,或者通过代理方式包装RestTemplate,实现动态获取最新Bundle。
问题2:降级设计中,应创建新的SSL Bundle实例并更新Singleton Bean,还是销毁后重新创建?
绝对不建议修改或销毁已存在的单例Bean。Spring容器的单例Bean设计为不可变对象,强行修改会引发线程安全问题,还可能导致容器内依赖关系混乱。
更合理的做法是:
- 临时构建新的
SslBundle和对应的RestTemplate来重试请求,不触碰原有的单例Bean; - 把原RestTemplate的Bundle获取逻辑改为延迟加载,比如注入
Provider<SslBundle>,每次请求时都获取最新的Bundle实例; - 若要复用Bean,可以用动态代理包装RestTemplate,让它每次请求都重新绑定最新的SSL上下文。
问题3:如何按需创建Spring Boot SSL Bundle?
可以通过SslBundleBuilder手动构建自定义SSL Bundle,不需要依赖配置文件,按需创建即可:
从本地文件构建
import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.ssl.SslBundleBuilder; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; public SslBundle createCustomSslBundle() throws Exception { Resource certResource = new ClassPathResource("cert/new-client-cert.pem"); Resource keyResource = new ClassPathResource("cert/new-private.key"); return SslBundleBuilder.create() .certificates(certResource) .privateKey(keyResource) .build(); }
从API返回的内容构建
import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.ssl.SslBundleBuilder; import java.nio.charset.StandardCharsets; public SslBundle createSslBundleFromApi(String certContent, String keyContent) throws Exception { return SslBundleBuilder.create() .certificates(certContent.getBytes(StandardCharsets.UTF_8)) .privateKey(keyContent.getBytes(StandardCharsets.UTF_8)) .build(); }
构建好的SslBundle可以直接传给RestTemplateBuilder.setSslBundle(),创建对应的RestTemplate用于请求即可。
内容的提问来源于stack exchange,提问作者Prabal Rakshit
相关产品推荐
相关产品推荐

