Spring Boot 3 中如何 配置 RestTemplate 支持SSL请求

67 阅读5分钟

在 Spring Boot 3 中配置 RestTemplate 以支持 SSL (HTTPS) 请求,特别是当需要信任自定义证书(例如自签名证书或私有 CA 颁发的证书)时,通常涉及配置底层的 HTTP 客户端库(如 Apache HttpClient 5,这是 Spring Boot 3 中 RestTemplate 的默认选择之一,如果它在类路径中)来使用自定义的 SSLContext

以下是几种常见的方法:

先决条件:

  1. TrustStore 文件: 你需要一个包含可信证书的 TrustStore 文件。这通常是一个 .jks (Java KeyStore) 或 .p12 / .pfx (PKCS12) 文件。你可以使用 Java 的 keytool 命令行工具来创建或管理这个文件,并将服务器的证书或其 CA 证书导入其中。
    • 示例 (导入证书到 JKS):
      keytool -importcert -alias <your_alias> -file <path_to_server_cert.cer> -keystore <path_to_your_truststore.jks> -storepass <your_truststore_password>
      
  2. TrustStore 密码: 创建或访问 TrustStore 文件时设置的密码。

方法一:使用 RestTemplateBuilder 和 Apache HttpClient 5 (推荐)

这是最灵活和推荐的方式,因为它允许你为特定的 RestTemplate 实例配置 SSL,而不是全局影响 JVM。Spring Boot 3 默认会使用 Apache HttpClient 5(如果可用)。

  1. 添加依赖 (如果需要明确指定): 虽然 spring-boot-starter-web 通常会间接引入 HTTP 客户端,但明确添加 httpclient5 可以确保其存在。

    <!-- pom.xml (Maven) -->
    <dependency>
        <groupId>org.apache.httpcomponents.client5</groupId>
        <artifactId>httpclient5</artifactId>
        <!-- 版本通常由 Spring Boot BOM 管理 -->
    </dependency>
    
    // build.gradle (Gradle)
    implementation 'org.apache.httpcomponents.client5:httpclient5'
    
  2. 配置 RestTemplate Bean: 创建一个 @Configuration 类来定义一个 RestTemplate bean,并通过 RestTemplateBuilder 配置 SSL。

    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.io.HttpClientConnectionManager;
    import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
    import org.apache.hc.client5.http.ssl.TrustSelfSignedStrategy; // 如果需要信任自签名证书
    import org.apache.hc.core5.ssl.SSLContexts;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.web.client.RestTemplateBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.io.Resource;
    import org.springframework.http.client.ClientHttpRequestFactory;
    import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
    import org.springframework.web.client.RestTemplate;
    
    import javax.net.ssl.SSLContext;
    import java.io.InputStream;
    import java.security.KeyStore;
    
    @Configuration
    public class RestTemplateConfig {
    
        @Value("${client.ssl.trust-store}")
        private Resource trustStore; // 从 application.properties 注入 TrustStore 路径
    
        @Value("${client.ssl.trust-store-password}")
        private String trustStorePassword;
    
        @Bean
        public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
    
            // 1. 加载 TrustStore 并创建 SSLContext
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); // 或者 "JKS", "PKCS12"
            try (InputStream inputStream = trustStore.getInputStream()) {
                keyStore.load(inputStream, trustStorePassword.toCharArray());
            }
    
            SSLContext sslContext = SSLContexts.custom()
                    // 加载自定义的 TrustStore
                    .loadTrustMaterial(keyStore, null) // 使用 null 表示使用 TrustStore 中的所有条目作为信任锚点
                    // 如果需要信任自签名证书 (可选,根据你的 TrustStore 内容决定)
                    // .loadTrustMaterial(keyStore, new TrustSelfSignedStrategy())
                    .build();
    
            // 2. 创建 SSLConnectionSocketFactory
            // 可以选择性地配置支持的协议和密码套件,以及主机名验证器
            // NoopHostnameVerifier.INSTANCE 会禁用主机名验证 (不推荐用于生产环境)
            // SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
            SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext); // 默认主机名验证
    
            // 3. 创建连接管理器
            HttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
                    .setSSLSocketFactory(sslSocketFactory)
                    .build();
    
            // 4. 创建 HttpClient
            CloseableHttpClient httpClient = HttpClients.custom()
                    .setConnectionManager(connectionManager)
                    // 可以添加其他 HttpClient 配置 (例如: 重试处理器, 请求/响应拦截器等)
                    .evictExpiredConnections() // 定期驱逐过期连接
                    .build();
    
            // 5. 创建使用自定义 HttpClient 的 RequestFactory
            ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
    
            // 6. 使用 RestTemplateBuilder 构建 RestTemplate
            return builder
                    .requestFactory(() -> requestFactory) // 关键:设置自定义的 RequestFactory
                    // 可以添加其他 RestTemplate 配置 (例如: 错误处理器, 拦截器等)
                    .build();
        }
    }
    
  3. 配置 application.propertiesapplication.yml: 指定 TrustStore 的路径和密码。

    # application.properties
    client.ssl.trust-store=classpath:certs/my-truststore.jks  # TrustStore 在 src/main/resources/certs 下
    # 或者使用文件系统路径: client.ssl.trust-store=file:/path/to/your/truststore.jks
    client.ssl.trust-store-password=your_password
    
    # application.yml
    client:
      ssl:
        trust-store: classpath:certs/my-truststore.jks
        trust-store-password: your_password
    

方法二:使用系统属性 (全局影响)

这种方法通过设置 JVM 系统属性来指定全局的 TrustStore。这会影响 JVM 中 所有 使用默认 SSLContext 的 SSL 连接,而不仅仅是你的 RestTemplate

  1. 设置系统属性: 你可以在启动应用程序时通过命令行参数设置:

    java -Djavax.net.ssl.trustStore=/path/to/your/truststore.jks \
         -Djavax.net.ssl.trustStorePassword=your_password \
         -Djavax.net.ssl.trustStoreType=JKS \  # 或 PKCS12
         -jar your-application.jar
    

    或者,在代码中 早期 设置(例如在 main 方法的开头),但这通常不推荐,因为它可能在 Spring Boot 完全初始化之前执行,并且可配置性较差:

    public static void main(String[] args) {
        System.setProperty("javax.net.ssl.trustStore", "/path/to/your/truststore.jks");
        System.setProperty("javax.net.ssl.trustStorePassword", "your_password");
        System.setProperty("javax.net.ssl.trustStoreType", "JKS");
        SpringApplication.run(YourApplication.class, args);
    }
    
  2. RestTemplate 使用: 如果使用了系统属性,并且你没有像方法一那样进行特殊的 RestTemplate 配置,那么默认情况下 RestTemplate(以及底层 HTTP 客户端)应该会使用这些全局设置。你只需正常地注入和使用 RestTemplate 即可。

    @Autowired
    private RestTemplate restTemplate;
    
    public void makeHttpsRequest() {
        String result = restTemplate.getForObject("https://your-secure-endpoint.com/api", String.class);
        // ...
    }
    

方法三:禁用 SSL 验证 (极不推荐,仅用于测试)

警告: 这种方法会完全禁用证书验证和主机名验证,使连接容易受到中间人攻击。绝对不要在生产环境中使用! 仅在受控的本地开发或测试环境中使用,并且要非常清楚相关的安全风险。

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.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.TrustAllStrategy; // 信任所有证书
import org.apache.hc.core5.ssl.SSLContextBuilder;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile; // 强烈建议只在特定 profile 下启用
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.SSLContext;

@Configuration
@Profile("disable-ssl-validation") // 例如,只在 'disable-ssl-validation' profile 激活时启用
public class InsecureRestTemplateConfig {

    @Bean
    public RestTemplate insecureRestTemplate(RestTemplateBuilder builder) throws Exception {

        // 1. 创建信任所有证书的 SSLContext
        SSLContext sslContext = SSLContextBuilder.create()
                .loadTrustMaterial(TrustAllStrategy.INSTANCE) // 关键:信任所有
                .build();

        // 2. 创建禁用主机名验证的 SSLConnectionSocketFactory
        SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(
                sslContext,
                NoopHostnameVerifier.INSTANCE // 关键:禁用主机名验证
        );

        // 3. 创建连接管理器
         HttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
                .setSSLSocketFactory(sslSocketFactory)
                .build();

        // 4. 创建 HttpClient
        CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(connectionManager)
                .evictExpiredConnections()
                .build();

        // 5. 创建 RequestFactory
        ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);

        // 6. 构建 RestTemplate
        return builder
                .requestFactory(() -> requestFactory)
                .build();
    }
}

总结与选择:

  • 推荐: 方法一 (使用 RestTemplateBuilder)。它提供了最好的灵活性和作用域控制,符合 Spring Boot 的配置习惯。
  • 简单场景/全局设置: 方法二 (系统属性)。如果你的应用中所有需要自定义信任的 SSL 连接都可以使用同一个 TrustStore,并且你接受这种全局影响,那么这是一个更简单的选择。
  • 仅限测试/临时: 方法三 (禁用验证)。极其危险,应严格限制使用场景,并尽快替换为正确的证书信任配置。

选择哪种方法取决于你的具体需求、安全策略以及你希望配置影响的范围。对于大多数生产应用程序,方法一是最佳实践。

扫码_搜索联合传播样式-标准色版.png