在 Spring Boot 3 中,如果你想使用 Apache HttpClient 5 作为 RestTemplate
的底层实现,并需要自定义 SSL 配置(例如,信任自签名证书、使用特定的信任库/密钥库),你需要创建一个自定义的 ClientHttpRequestFactory
。
推荐的方式是使用 RestTemplateBuilder
来配置 RestTemplate
,并为其提供一个配置了自定义 SSLContext 的 HttpClient 实例。
以下是详细步骤和示例代码:
1. 添加依赖
首先,确保你的 pom.xml
(Maven) 或 build.gradle
(Gradle) 文件中包含了 HttpClient 5 的依赖。Spring Boot 的 spring-boot-starter-web
默认不包含它,你需要显式添加:
Maven (pom.xml
):
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<!-- Spring Boot 会管理版本,如果需要可以指定 -->
<!-- <version>5.x.x</version> -->
</dependency>
Gradle (build.gradle
):
implementation 'org.apache.httpcomponents.client5:httpclient5'
2. 创建自定义 SSLContext
你需要根据你的具体需求(信任所有证书、信任特定证书、使用客户端证书等)来创建 SSLContext
。
- 信任所有证书 (仅用于测试,不安全!)
- 加载自定义信任库 (TrustStore)
- 加载自定义密钥库 (KeyStore,用于客户端认证)
示例:加载自定义 TrustStore (例如 mytruststore.jks
)
假设你有一个名为 mytruststore.jks
的 JKS 格式的信任库文件,密码是 password
,并且它位于 classpath 下。
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.SSLConnectionSocketFactoryBuilder;
import org.apache.hc.client5.http.ssl.TrustSelfSignedStrategy; // 如果信任自签名
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.ssl.TrustStrategy; // 用于信任所有等策略
import javax.net.ssl.SSLContext;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
// ... other imports
private SSLContext createCustomSslContext() {
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); // 或者 "JKS", "PKCS12" 等
// --- 从 Classpath 加载 TrustStore ---
// 推荐使用 ResourceLoader 来加载资源
// @Autowired private ResourceLoader resourceLoader;
// Resource trustStoreResource = resourceLoader.getResource("classpath:mytruststore.jks");
// try (InputStream trustStoreStream = trustStoreResource.getInputStream()) {
// trustStore.load(trustStoreStream, "password".toCharArray());
// }
// --- 或者直接使用 ClassLoader (简化示例) ---
try (InputStream trustStoreStream = getClass().getClassLoader().getResourceAsStream("mytruststore.jks")) {
if (trustStoreStream == null) {
throw new RuntimeException("Truststore 'mytruststore.jks' not found in classpath");
}
trustStore.load(trustStoreStream, "password".toCharArray());
}
// --- 创建 SSLContext ---
// 1. 信任加载的 TrustStore 中的证书
SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial(trustStore, null) // 第二个参数是 TrustStrategy,null 表示使用标准信任链验证
.build();
// 2. 或者:信任自签名证书 (如果 TrustStore 包含自签名 CA)
// SSLContext sslContext = SSLContexts.custom()
// .loadTrustMaterial(trustStore, new TrustSelfSignedStrategy())
// .build();
// 3. 或者:信任所有证书 (非常不安全,仅用于测试)
// TrustStrategy acceptingTrustStrategy = (cert, authType) -> true;
// SSLContext sslContext = SSLContexts.custom()
// .loadTrustMaterial(null, acceptingTrustStrategy)
// .build();
// --- 如果需要加载 KeyStore (客户端证书) ---
// KeyStore keyStore = KeyStore.getInstance("PKCS12"); // 或 JKS
// try (InputStream keyStoreStream = getClass().getClassLoader().getResourceAsStream("mykeystore.p12")) {
// keyStore.load(keyStoreStream, "keyPassword".toCharArray());
// }
// sslContext = SSLContexts.custom()
// .loadTrustMaterial(trustStore, null) // 信任库
// .loadKeyMaterial(keyStore, "keyStorePassword".toCharArray()) // 密钥库
// .build();
return sslContext;
} catch (Exception e) {
// 处理异常,例如记录日志或抛出 RuntimeException
throw new RuntimeException("Failed to create custom SSL context", e);
}
}
3. 创建配置了 SSL 的 HttpClient
使用上面创建的 SSLContext
来配置 SSLConnectionSocketFactory
,然后构建 HttpClient
。
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.SSLConnectionSocketFactoryBuilder;
import org.apache.hc.core5.http.URIScheme;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
import org.apache.hc.core5.pool.PoolReusePolicy;
import org.apache.hc.core5.reactor.ssl.TlsDetails;
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.util.Timeout;
// ... 其他 imports
private CloseableHttpClient createHttpClientWithCustomSsl() {
try {
SSLContext sslContext = createCustomSslContext(); // 调用上面创建 SSLContext 的方法
// --- 配置 SSLConnectionSocketFactory ---
SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
.setSslContext(sslContext)
// 可选:禁用主机名验证 (仅用于测试,不安全)
// .setHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.build();
// --- 创建连接管理器 ---
HttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
.setSSLSocketFactory(sslSocketFactory) // 应用 SSL Socket Factory
// 可选:其他连接池配置
// .setMaxConnTotal(100)
// .setMaxConnPerRoute(20)
// .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(Timeout.ofSeconds(30)).build())
.build();
// --- 构建 HttpClient ---
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
// 可选:设置请求配置 (超时等)
// .setDefaultRequestConfig(RequestConfig.custom()
// .setConnectTimeout(Timeout.ofSeconds(5))
// .setResponseTimeout(Timeout.ofSeconds(30))
// .build())
// 可选: 禁用重试
// .disableAutomaticRetries()
// 可选: 禁用内容压缩
// .disableContentCompression()
.build();
return httpClient;
} catch (Exception e) {
throw new RuntimeException("Failed to create HttpClient with custom SSL", e);
}
}
4. 配置 RestTemplate Bean
在你的 Spring Boot 配置类 (@Configuration
) 中,定义一个 RestTemplate
Bean,并使用 RestTemplateBuilder
来注入上面创建的自定义 HttpClient
。
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
// ... 其他 SSL 和 HttpClient 相关 imports
@Configuration
public class RestTemplateConfig {
// 如果需要从 classpath 加载资源,注入 ResourceLoader
// @Autowired
// private ResourceLoader resourceLoader;
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
// 1. 创建自定义的 HttpClient
CloseableHttpClient httpClient = createHttpClientWithCustomSsl();
// 2. 创建 HttpComponentsClientHttpRequestFactory
// 这个工厂会使用我们自定义的 HttpClient
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
// 可选: 设置连接超时和读取超时 (也可以在 HttpClient 中配置)
// requestFactory.setConnectTimeout(5000); // 5 seconds
// requestFactory.setReadTimeout(30000); // 30 seconds
// 3. 使用 RestTemplateBuilder 应用自定义的 RequestFactory
return builder
.requestFactory(() -> requestFactory) // 提供自定义工厂
.build();
}
// --- 将上面创建 SSLContext 和 HttpClient 的方法放在这里 ---
private SSLContext createCustomSslContext() {
// ... (实现如上)
try {
// 示例: 信任所有证书 (仅测试)
TrustStrategy acceptingTrustStrategy = (cert, authType) -> true;
return SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
} catch (Exception e) {
throw new RuntimeException("Failed to create custom SSL context", e);
}
}
private CloseableHttpClient createHttpClientWithCustomSsl() {
// ... (实现如上)
try {
SSLContext sslContext = createCustomSslContext();
SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
.setSslContext(sslContext)
.setHostnameVerifier(NoopHostnameVerifier.INSTANCE) // 仅测试
.build();
HttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
.setSSLSocketFactory(sslSocketFactory)
.build();
return HttpClients.custom()
.setConnectionManager(connectionManager)
.build();
} catch (Exception e) {
throw new RuntimeException("Failed to create HttpClient with custom SSL", e);
}
}
// --- 定义 TrustStrategy ---
private static class TrustAllStrategy implements TrustStrategy {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
}
}
解释:
createCustomSslContext()
: 负责根据你的策略(信任库、密钥库、信任所有等)构建SSLContext
。根据实际情况修改这部分代码。createHttpClientWithCustomSsl()
: 使用上面创建的SSLContext
初始化SSLConnectionSocketFactory
,然后用这个工厂配置HttpClientConnectionManager
(推荐使用连接池PoolingHttpClientConnectionManagerBuilder
),最后构建CloseableHttpClient
。restTemplate(RestTemplateBuilder builder)
: 这是 Spring Boot 配置RestTemplate
的标准方式。- 我们先调用
createHttpClientWithCustomSsl()
创建一个带有自定义 SSL 配置的HttpClient
实例。 - 然后创建一个
HttpComponentsClientHttpRequestFactory
实例,并将自定义的HttpClient
设置给它。HttpComponentsClientHttpRequestFactory
是 Spring 提供的适配器,用于将 Apache HttpClient 集成到 Spring 的ClientHttpRequestFactory
体系中。 - 最后,通过
builder.requestFactory(() -> requestFactory)
将这个自定义的工厂提供给RestTemplateBuilder
。注意这里使用 lambda 表达式提供了一个Supplier<ClientHttpRequestFactory>
。 builder.build()
返回最终配置好的RestTemplate
实例。
- 我们先调用
现在,通过 @Autowired
注入这个 RestTemplate
Bean 的任何地方,它都会使用配置了自定义 SSL 的 HttpClient 5 来发起 HTTPS 请求。
重要安全提示:
- 避免信任所有证书 (
TrustAllStrategy
) 和禁用主机名验证 (NoopHostnameVerifier
) 在生产环境中使用! 这会使你的应用程序容易受到中间人攻击。只应在严格控制的测试环境中使用。 - 安全地管理密码: 不要将信任库/密钥库的密码硬编码在代码中。使用 Spring Boot 的配置属性 (
application.properties
或application.yml
),并通过@Value
或配置类注入,或者使用更安全的秘密管理工具。 - 使用正确的信任库: 确保你的信任库只包含你确实信任的证书颁发机构 (CA) 或服务器证书。
选择最适合你场景的 SSLContext
创建方式,并确保资源文件(如 .jks
, .p12
文件)能够被正确加载(通常放在 src/main/resources
下)。