在 Spring Boot 3 中配置 RestTemplate
以支持 SSL (HTTPS) 请求,特别是当需要信任自定义证书(例如自签名证书或私有 CA 颁发的证书)时,通常涉及配置底层的 HTTP 客户端库(如 Apache HttpClient 5,这是 Spring Boot 3 中 RestTemplate
的默认选择之一,如果它在类路径中)来使用自定义的 SSLContext
。
以下是几种常见的方法:
先决条件:
- 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>
- 示例 (导入证书到 JKS):
- TrustStore 密码: 创建或访问 TrustStore 文件时设置的密码。
方法一:使用 RestTemplateBuilder
和 Apache HttpClient 5 (推荐)
这是最灵活和推荐的方式,因为它允许你为特定的 RestTemplate
实例配置 SSL,而不是全局影响 JVM。Spring Boot 3 默认会使用 Apache HttpClient 5(如果可用)。
-
添加依赖 (如果需要明确指定): 虽然
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'
-
配置
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(); } }
-
配置
application.properties
或application.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
。
-
设置系统属性: 你可以在启动应用程序时通过命令行参数设置:
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); }
-
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,并且你接受这种全局影响,那么这是一个更简单的选择。
- 仅限测试/临时: 方法三 (禁用验证)。极其危险,应严格限制使用场景,并尽快替换为正确的证书信任配置。
选择哪种方法取决于你的具体需求、安全策略以及你希望配置影响的范围。对于大多数生产应用程序,方法一是最佳实践。