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

Spring Boot 2.7迁移至3.1.4:如何用cloud://路径配置SSLBundle

Spring Boot 3.1.4 迁移:从Config Server仓库加载SSL证书到SSLBundle的解决方案

问题背景

Spring Boot从2.7升级到3.1.4后,HttpClient4同步更新为HttpClient5,原有手动加载JKS创建SSLContext的逻辑失效。尝试使用Spring Boot 3.x的SSLBundle特性时,无法通过cloud://协议加载Config Server仓库中的.p12/.key/.crt证书文件,同时主机名验证(hostnameverifier)功能异常。


解决方案

1. 自定义资源解析器支持cloud://协议

Spring默认ResourceLoader不识别cloud://协议,需扩展ProtocolResolver对接Config Server资源加载逻辑:

步骤1:实现ProtocolResolver

import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.ProtocolResolver;
import org.springframework.cloud.config.client.ConfigServerConfigDataLoader;
import org.springframework.cloud.config.client.ConfigServerConfigDataResource;
import org.springframework.util.Assert;

public class CloudProtocolResolver implements ProtocolResolver {

    private final ConfigServerConfigDataLoader configDataLoader;

    public CloudProtocolResolver(ConfigServerConfigDataLoader configDataLoader) {
        Assert.notNull(configDataLoader, "ConfigServerConfigDataLoader must not be null");
        this.configDataLoader = configDataLoader;
    }

    @Override
    public Resource resolve(String location, ResourceLoader resourceLoader) {
        if (location.startsWith("cloud://")) {
            String resourcePath = location.substring("cloud://".length());
            // 构建Config Server资源请求,根据实际Config Server配置调整参数
            ConfigServerConfigDataResource configResource = new ConfigServerConfigDataResource(
                ConfigServerConfigDataResource.ResourceType.PROPERTIES,
                resourcePath,
                null, null, null, null
            );
            try {
                return configDataLoader.load(configResource, null).iterator().next().getResource();
            } catch (Exception e) {
                throw new RuntimeException("Failed to load cloud resource: " + location, e);
            }
        }
        return null;
    }
}

步骤2:注册解析器到Spring容器

在启动类中注册自定义协议解析器:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.client.ConfigServerConfigDataLoader;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class YourApplication {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(YourApplication.class);
        app.addProtocolResolver(new CloudProtocolResolver(app.getApplicationContext().getBean(ConfigServerConfigDataLoader.class)));
        app.run(args);
    }

    // 或者通过@Bean方式注册
    @Bean
    public ProtocolResolver cloudProtocolResolver(ConfigServerConfigDataLoader configDataLoader) {
        return new CloudProtocolResolver(configDataLoader);
    }
}

完成后即可直接使用原配置:

spring.ssl.bundle.jks.server.keystore.location=cloud://test.p12
spring.ssl.bundle.pem.client.keystore.certificate=cloud://client.crt
spring.ssl.bundle.pem.client.keystore.private-key=cloud://client.key

2. 手动构建SSLBundle(替代配置方式)

若自定义协议解析器存在问题,可直接通过代码从Config Server加载证书并构建SSLBundle:

步骤1:封装Config Server资源加载逻辑

import org.springframework.cloud.config.client.ConfigClientProperties;
import org.springframework.cloud.config.client.ConfigServerConfigDataLoader;
import org.springframework.cloud.config.client.ConfigServerConfigDataResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

@Service
public class SslResourceLoader {

    private final ConfigServerConfigDataLoader configDataLoader;
    private final ConfigClientProperties configClientProperties;

    public SslResourceLoader(ConfigServerConfigDataLoader configDataLoader, ConfigClientProperties configClientProperties) {
        this.configDataLoader = configDataLoader;
        this.configClientProperties = configClientProperties;
    }

    public Resource loadCloudResource(String path) throws Exception {
        ConfigServerConfigDataResource resource = new ConfigServerConfigDataResource(
            ConfigServerConfigDataResource.ResourceType.PROPERTIES,
            path,
            configClientProperties.getProfile(),
            configClientProperties.getLabel(),
            configClientProperties.getName(),
            null
        );
        return configDataLoader.load(resource, null).iterator().next().getResource();
    }
}

步骤2:构建JKS类型SSLBundle

import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundleBuilder;
import org.springframework.boot.ssl.SslStoreBundle;
import org.springframework.boot.ssl.jks.JksSslStoreBundle;
import org.springframework.stereotype.Component;

@Component
public class JksSslBundleBuilder {

    private final SslResourceLoader sslResourceLoader;

    public JksSslBundleBuilder(SslResourceLoader sslResourceLoader) {
        this.sslResourceLoader = sslResourceLoader;
    }

    public SslBundle build(String keystorePath, String keystorePassword) throws Exception {
        Resource keystoreResource = sslResourceLoader.loadCloudResource(keystorePath);
        SslStoreBundle storeBundle = JksSslStoreBundle.of(
            keystoreResource.getInputStream(),
            keystorePassword.toCharArray(),
            null, null
        );
        return SslBundleBuilder.create()
            .storeBundle(storeBundle)
            // 配置主机名验证,解决原有验证失效问题
            .hostnameVerifier((hostname, session) -> 
                javax.net.ssl.HttpsURLConnection.getDefaultHostnameVerifier().verify(hostname, session)
            )
            .build();
    }
}

步骤3:构建PEM类型SSLBundle

import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundleBuilder;
import org.springframework.boot.ssl.SslStoreBundle;
import org.springframework.boot.ssl.pem.PemSslStoreBundle;
import org.springframework.stereotype.Component;

@Component
public class PemSslBundleBuilder {

    private final SslResourceLoader sslResourceLoader;

    public PemSslBundleBuilder(SslResourceLoader sslResourceLoader) {
        this.sslResourceLoader = sslResourceLoader;
    }

    public SslBundle build(String certPath, String keyPath, String keyPassword) throws Exception {
        Resource certResource = sslResourceLoader.loadCloudResource(certPath);
        Resource keyResource = sslResourceLoader.loadCloudResource(keyPath);
        SslStoreBundle storeBundle = PemSslStoreBundle.of(
            certResource.getInputStream(),
            keyResource.getInputStream(),
            keyPassword != null ? keyPassword.toCharArray() : null
        );
        return SslBundleBuilder.create()
            .storeBundle(storeBundle)
            .hostnameVerifier((hostname, session) -> 
                javax.net.ssl.HttpsURLConnection.getDefaultHostnameVerifier().verify(hostname, session)
            )
            .build();
    }
}

步骤4:注入SSLBundle到HttpClient5

import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class HttpClientConfig {

    @Bean
    public CloseableHttpClient httpClient(SslBundle sslBundle) {
        SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
            .setSslContext(sslBundle.createSslContext())
            .setHostnameVerifier(sslBundle.getHostnameVerifier())
            .build();
        return HttpClients.custom()
            .setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create()
                .setSSLSocketFactory(sslSocketFactory)
                .build())
            .build();
    }
}

3. 主机名验证注意事项

  • 生产环境禁止直接返回true跳过验证,必须使用默认验证器或自定义符合业务安全要求的验证逻辑
  • 若需兼容特定域名,可在hostnameVerifier中添加白名单判断

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 02:15:09