Spring Boot新版本中用SslBundle程序化配置SSL密钥库与信任库
程序化配置Spring Boot HTTPS(基于SslBundle替代SslStoreProvider)
问题背景
需要在Spring Boot项目中通过Java代码配置HTTPS的keystore和truststore,不依赖application.properties文件。此前使用Spring 3.1.3的SslStoreProvider实现可正常运行,但该类在Spring 3.2.3被弃用,3.3.3及以后版本彻底移除,官方推荐使用SslBundle替代。自行尝试的SslBundle配置无法发起HTTPS请求,寻求正确实现方式。
旧的SslStoreProvider实现(可正常运行)
@Bean public JettyServletWebServerFactory jettyConfigBean() { JettyServletWebServerFactory jef = new JettyServletWebServerFactory(); System.out.println("JettyServletWebServerFactory"); System.out.println("Exec Prakrath code!"); jef.setSslStoreProvider(new SslStoreProvider() { char[] password = "changeit".toCharArray(); @Override public KeyStore getKeyStore() throws Exception { System.out.println("downloadCerts and copyCerts initial load"); System.out.println("SslStoreProvider getKeyStore is called"); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(new FileInputStream("/Users/prakrath/personal/teluskojava/servletjetty/src/main/resources/keystore.jks"), password); return keyStore; } @Override public KeyStore getTrustStore() throws Exception { System.out.println("SslStoreProvider getTrustStore is called"); KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustStore.load(new FileInputStream("/Users/prakrath/personal/teluskojava/servletjetty/src/main/resources/keystore.jks"), password); return trustStore; } }); return jef; }
自行尝试的SslBundle实现(无法正常工作)
@Bean public JettyServletWebServerFactory jettyConfigBean() { JettyServletWebServerFactory jef = new JettyServletWebServerFactory(); System.out.println("JettyServletWebServerFactory"); System.out.println("Exec Prakrath code!"); // 保留了已弃用的SslStoreProvider jef.setSslStoreProvider(new SslStoreProvider() { char[] password = "changeit".toCharArray(); @Override public KeyStore getKeyStore() throws Exception { System.out.println("downloadCerts and copyCerts initial load"); System.out.println("SslStoreProvider getKeyStore is called"); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(new FileInputStream("/Users/prakrath/personal/teluskojava/servletjetty/src/main/resources/keystore.jks"), password); return keyStore; } @Override public KeyStore getTrustStore() throws Exception { System.out.println("SslStoreProvider getTrustStore is called"); KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustStore.load(new FileInputStream("/Users/prakrath/personal/teluskojava/servletjetty/src/main/resources/keystore.jks"), password); return trustStore; } }); // 新增的SslBundles实现 jef.setSslBundles(new SslBundles() { @Override public SslBundle getBundle(String name) throws NoSuchSslBundleException { char[] password = "changeit".toCharArray(); System.out.println("Inside SSL BUNDLE prakrath code!"); return SslBundle.of(SslStoreBundle.of( getKeyStore(password), new String(password), getTrustStore(password) )); } private KeyStore getKeyStore(char[] password) { System.out.println("downloadCerts and copyCerts initial load"); System.out.println("SslStoreProvider getKeyStore is called"); try { KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(new FileInputStream("/Users/prakrath/personal/teluskojava/servletjetty/src/main/resources/keystore.jks"), password); return keyStore; } catch (Exception e) { throw new RuntimeException(e); } } private KeyStore getTrustStore(char[] password) { System.out.println("SslStoreProvider getTrustStore is called"); try { KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustStore.load(new FileInputStream("/Users/prakrath/personal/teluskojava/servletjetty/src/main/resources/keystore.jks"), password); return trustStore; } catch (Exception e) { throw new RuntimeException(e); } } @Override public void addBundleUpdateHandler(String name, Consumer<SslBundle> updateHandler) throws NoSuchSslBundleException { } }); return jef; }
正确的SslBundle实现方案
问题分析
之前的实现存在两个核心问题:
- 同时配置了已弃用的
SslStoreProvider和新的SslBundles,两者冲突导致Spring无法正确识别新配置。 SslBundles的getBundle方法没有处理Spring Boot默认使用的**"server"** bundle名称,导致无法加载正确的SSL配置。
代码实现
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; import org.springframework.boot.web.server.SslBundle; import org.springframework.boot.web.server.SslBundles; import org.springframework.boot.web.server.SslStoreBundle; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ResourceLoader; import javax.annotation.Resource; import java.io.InputStream; import java.security.KeyStore; import java.util.function.Consumer; @Configuration public class HttpsConfig { @Resource private ResourceLoader resourceLoader; private static final char[] STORE_PASSWORD = "changeit".toCharArray(); private static final char[] KEY_PASSWORD = "changeit".toCharArray(); // 若密钥密码与存储密码不同,需单独设置 @Bean public JettyServletWebServerFactory jettyServletWebServerFactory() { JettyServletWebServerFactory factory = new JettyServletWebServerFactory(); // 移除已弃用的SslStoreProvider配置,仅保留SslBundles factory.setSslBundles(new SslBundles() { @Override public SslBundle getBundle(String name) { // Spring Boot默认使用"server"作为服务器SSL配置的bundle名称 if ("server".equals(name)) { try { // 加载keystore KeyStore keyStore = loadKeyStore("classpath:keystore.jks", STORE_PASSWORD); // 加载truststore(此处复用keystore,实际可替换为单独的truststore文件) KeyStore trustStore = loadKeyStore("classpath:keystore.jks", STORE_PASSWORD); // 构建SslStoreBundle SslStoreBundle storeBundle = SslStoreBundle.of( keyStore, new String(KEY_PASSWORD), trustStore, new String(STORE_PASSWORD) ); // 构建完整的SslBundle,可添加额外SSL配置(如协议、密码套件等) return SslBundle.builder() .storeBundle(storeBundle) .build(); } catch (Exception e) { throw new RuntimeException("Failed to create SSL Bundle", e); } } throw new IllegalArgumentException("Unknown SSL bundle name: " + name); } @Override public void addBundleUpdateHandler(String name, Consumer<SslBundle> updateHandler) { // 若无需动态更新SSL配置,可留空 } }); return factory; } private KeyStore loadKeyStore(String resourcePath, char[] password) throws Exception { KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); try (InputStream inputStream = resourceLoader.getResource(resourcePath).getInputStream()) { keyStore.load(inputStream, password); } return keyStore; } }
关键说明
- 移除旧配置:完全删除
setSslStoreProvider的调用,避免新旧配置冲突。 - 默认Bundle名称:Spring Boot默认使用"server"作为服务器SSL配置的bundle名称,必须在
getBundle中处理该名称。 - 资源加载优化:使用
ResourceLoader加载classpath下的资源,避免硬编码文件路径,提升代码可移植性。 - 安全的密码处理:优先使用
char[]存储密码,避免String的不可变特性带来的安全风险(示例中因API要求转换为String,实际可根据Spring版本调整)。 - 灵活配置:通过
SslBundle.builder()可添加更多SSL参数,如支持的协议、密码套件、客户端认证要求等。
内容的提问来源于stack exchange,提问作者rishi rathore
相关产品推荐
相关产品推荐

