You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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实现方案

问题分析

之前的实现存在两个核心问题:

  1. 同时配置了已弃用的SslStoreProvider和新的SslBundles,两者冲突导致Spring无法正确识别新配置。
  2. 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;
    }
}

关键说明

  1. 移除旧配置:完全删除setSslStoreProvider的调用,避免新旧配置冲突。
  2. 默认Bundle名称:Spring Boot默认使用"server"作为服务器SSL配置的bundle名称,必须在getBundle中处理该名称。
  3. 资源加载优化:使用ResourceLoader加载classpath下的资源,避免硬编码文件路径,提升代码可移植性。
  4. 安全的密码处理:优先使用char[]存储密码,避免String的不可变特性带来的安全风险(示例中因API要求转换为String,实际可根据Spring版本调整)。
  5. 灵活配置:通过SslBundle.builder()可添加更多SSL参数,如支持的协议、密码套件、客户端认证要求等。

内容的提问来源于stack exchange,提问作者rishi rathore

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 19:48:11