You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何让Spring Boot使用自定义加载的KeyStore/TrustStore覆盖双向SSL配置

解决Spring Boot自定义加载KeyStore/TrustStore实现双向SSL的问题

首先,咱们得先搞清楚你遇到的问题根源:Spring Boot默认的SSL配置逻辑是从本地文件系统加载密钥库,它会把你在application.yml里填的URL直接当成文件路径去读取,这就导致读出来的内容根本不是正确的PKCS12/JKS证书文件,自然会出现DER格式解析失败的错误。而你自己写的代码是通过HTTP请求正确获取了证书的字节流,所以能正常加载。

要让Spring Boot使用你自定义加载的KeyStore/TrustStore对象,咱们可以通过禁用默认SSL自动配置+手动配置Web容器SSL上下文的方式实现,下面是具体步骤:


1. 调整配置文件

先把原来application.yml里的server.ssl相关配置删掉或者注释掉,只保留端口配置(因为我们要完全自定义SSL逻辑):

server:
  port: 8443
# 原来的server.ssl配置全部移除,交给自定义代码处理

2. 编写自定义SSL配置类

创建一个配置类,通过WebServerFactoryCustomizer来直接操控Tomcat的SSL配置,把你自定义加载的KeyStore/TrustStore注入进去:

import org.apache.catalina.connector.Connector;
import org.apache.coyote.http11.Http11NioProtocol;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

import java.io.InputStream;
import java.net.URI;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;

@Configuration
public class CustomSslConfig {

    // 建议把这些配置项放到application.yml里,用@Value注入,更灵活
    @Value("${custom.ssl.key-store-url}")
    private String keyStoreUrl;

    @Value("${custom.ssl.key-store-password}")
    private String keyStorePassword;

    @Value("${custom.ssl.trust-store-url}")
    private String trustStoreUrl;

    @Value("${custom.ssl.trust-store-password}")
    private String trustStorePassword;

    @Bean
    public RestTemplate sslRestTemplate() {
        return new RestTemplate();
        // 如果你的配置服务器需要认证,在这里给RestTemplate添加认证拦截器即可
    }

    @Bean
    public WebServerFactoryCustomizer<TomcatServletWebServerFactory> customSslCustomizer() {
        return factory -> {
            factory.addConnectorCustomizers((Connector connector) -> {
                Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
                protocol.setSSLEnabled(true);
                connector.setScheme("https");
                connector.setSecure(true);

                try {
                    // 加载你自定义的KeyStore和TrustStore
                    KeyStore keyStore = loadKeyStore(keyStoreUrl, keyStorePassword, "PKCS12");
                    KeyStore trustStore = loadKeyStore(trustStoreUrl, trustStorePassword, "JKS");

                    // 初始化密钥管理器和信任管理器
                    javax.net.ssl.KeyManagerFactory keyManagerFactory =
                            javax.net.ssl.KeyManagerFactory.getInstance(javax.net.ssl.KeyManagerFactory.getDefaultAlgorithm());
                    keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());

                    javax.net.ssl.TrustManagerFactory trustManagerFactory =
                            javax.net.ssl.TrustManagerFactory.getInstance(javax.net.ssl.TrustManagerFactory.getDefaultAlgorithm());
                    trustManagerFactory.init(trustStore);

                    // 创建自定义SSL上下文
                    javax.net.ssl.SSLContext sslContext = javax.net.ssl.SSLContext.getInstance("TLS");
                    sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);

                    // 把SSL上下文设置给Tomcat协议处理器
                    protocol.setSslContext(sslContext);
                    // 开启双向认证,对应原来的client-auth: need
                    protocol.setClientAuth("need");

                } catch (Exception e) {
                    throw new RuntimeException("Failed to initialize custom SSL configuration", e);
                }
            });
        };
    }

    // 封装通用的KeyStore加载方法
    private KeyStore loadKeyStore(String url, String password, String type) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
        KeyStore keyStore = KeyStore.getInstance(type);
        try (InputStream is = readCertificateFromUrl(URI.create(url))) {
            keyStore.load(is, password.toCharArray());
        }
        return keyStore;
    }

    // 你的证书读取逻辑(可以复用你原来的代码)
    private InputStream readCertificateFromUrl(URI uri) throws IOException {
        return sslRestTemplate().getForObject(uri, InputStream.class);
        // 或者用你原来的RequestEntity方式:
        /*
        RequestEntity<Void> requestEntity = RequestEntity.get(uri)
                .accept(org.springframework.http.MediaType.APPLICATION_OCTET_STREAM)
                .build();
        return sslRestTemplate().exchange(requestEntity, InputStream.class).getBody();
        */
    }
}

3. 补充配置项到application.yml

把原来的证书相关配置移到自定义的配置项里:

custom:
  ssl:
    key-store-url: http://{config server url}/keystore.p12
    key-store-password: {keystore password}
    trust-store-url: http://{config server url}/truststore.jks
    trust-store-password: {truststore password}

关键说明

  1. 禁用默认SSL自动配置:通过移除server.ssl配置,Spring Boot不会自动初始化默认的SSL上下文,转而使用我们自定义的配置。
  2. 直接操控Web容器:通过WebServerFactoryCustomizer,我们可以直接访问Tomcat的底层连接器和协议处理器,把自定义的SSL上下文注入进去。
  3. 复用你的证书加载逻辑:保留了你原来从URL加载证书的代码,确保证书能正确获取和解析。
  4. 双向认证开启:通过protocol.setClientAuth("need")实现了和原来client-auth: need一样的双向认证效果。

如果你的应用使用的是Jetty或Undertow容器,只需要替换对应的WebServerFactoryCustomizer实现类即可,核心逻辑(加载证书、创建SSL上下文)是一致的。

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

火山引擎 最新活动