如何将RegistryBuilder注入Feign的HttpClientFeignLoadBalancedConfiguration?
解决Feign(Apache HttpClient底层)双向TLS握手失败及RegistryBean注入问题
我来帮你梳理下这个问题的核心和解决办法:
首先,你遇到的handshake_failure和密钥库未被使用的问题,根源确实是**setSSLSocketFactory和setConnectionManager的冲突**——当你为HttpClient指定了连接管理器后,单独设置的SSLSocketFactory会被忽略,因为连接管理器内部已经维护了一套套接字工厂的注册逻辑。
你的初始配置代码确实存在这个问题:
@Primary public Client feignClient( CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory, BeanFactory beanFactory, @Qualifier("customApacheHttpClient") ApacheHttpClient apacheHttpClient) { return new TraceLoadBalancerFeignClient(apacheHttpClient, cachingFactory, clientFactory, beanFactory); } /** * @param connectionManager is autowired from HttpClientFeignLoadBalancedConfiguration.HttpClientFeignConfiguration */ @Bean(name = "customApacheHttpClient") public ApacheHttpClient apacheHttpClient( HttpClientConnectionManager connectionManager, FeignHttpClientProperties httpClientProperties) { RequestConfig defaultRequestConfig = RequestConfig.custom() .setConnectTimeout(httpClientProperties.getConnectionTimeout()) .setRedirectsEnabled(httpClientProperties.isFollowRedirects()) .build(); return new ApacheHttpClient( HttpClients.custom() .setConnectionManager(connectionManager) .setDefaultRequestConfig(defaultRequestConfig) .setSSLSocketFactory(getSSLConnectionSocketFactory()) .build()); }
之后你尝试移除setSSLSocketFactory并添加RegistryBuilder Bean,但这个Bean没被HttpClientFeignLoadBalancedConfiguration.HttpClientFeignConfiguration识别,这是因为Spring内部配置类的Bean扫描优先级和范围问题,下面是具体的解决思路:
方案1:让自定义RegistryBuilder优先被装配
直接给你的RegistryBuilder Bean加上@Primary注解,让Spring在自动装配时优先选择你的实现,而不是默认的:
@Bean @Primary public RegistryBuilder registryBuilder(SSLConnectionSocketFactory sslConnectionSocketFactory) { return RegistryBuilder.create() .register("http", PlainConnectionSocketFactory.INSTANCE) .register("https", sslConnectionSocketFactory) .register("https2", sslConnectionSocketFactory); }
这个方法简单直接,但要注意如果有其他地方也使用RegistryBuilder,可能会受到影响。
方案2:构建包含SSL配置的自定义连接管理器(推荐)
既然连接管理器会覆盖套接字工厂的设置,那我们直接构建一个带有SSL配置的连接管理器,替代默认的连接管理器:
步骤1:创建SSLConnectionSocketFactory(加载密钥库/信任库)
@Bean public SSLConnectionSocketFactory sslConnectionSocketFactory() throws Exception { // 加载客户端密钥库(双向TLS必须,存储客户端证书) KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); try (InputStream keyStoreStream = new FileInputStream("path/to/your/client-keystore.jks")) { keyStore.load(keyStoreStream, "keystore-password".toCharArray()); } // 加载信任库(存储服务端证书,验证服务端身份) KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); try (InputStream trustStoreStream = new FileInputStream("path/to/your/truststore.jks")) { trustStore.load(trustStoreStream, "truststore-password".toCharArray()); } // 构建SSL上下文,绑定密钥库和信任库 SSLContext sslContext = SSLContexts.custom() .loadKeyMaterial(keyStore, "key-password".toCharArray()) // 客户端证书的密钥密码 .loadTrustMaterial(trustStore, null) .build(); // 强制使用TLSv1.2协议,避免不安全的协议 return new SSLConnectionSocketFactory( sslContext, new String[]{"TLSv1.2"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier()); }
步骤2:构建带SSL配置的连接管理器
@Bean public HttpClientConnectionManager customConnectionManager(SSLConnectionSocketFactory sslSocketFactory) { Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create() .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", sslSocketFactory) .build(); PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); // 根据业务需求设置连接池参数 connectionManager.setMaxTotal(200); connectionManager.setDefaultMaxPerRoute(20); return connectionManager; }
步骤3:修改ApacheHttpClient Bean,使用自定义连接管理器
@Bean(name = "customApacheHttpClient") public ApacheHttpClient apacheHttpClient( @Qualifier("customConnectionManager") HttpClientConnectionManager connectionManager, FeignHttpClientProperties httpClientProperties) { RequestConfig defaultRequestConfig = RequestConfig.custom() .setConnectTimeout(httpClientProperties.getConnectionTimeout()) .setRedirectsEnabled(httpClientProperties.isFollowRedirects()) .build(); return new ApacheHttpClient( HttpClients.custom() .setConnectionManager(connectionManager) .setDefaultRequestConfig(defaultRequestConfig) .build()); }
方案3:使用Feign自定义配置类(最清晰的方式)
不要依赖Spring Cloud内部的HttpClientFeignLoadBalancedConfiguration,而是为你的Feign客户端单独创建配置类,这样更易维护,也避免内部类的依赖问题:
自定义Feign TLS配置类
@Configuration public class FeignTwoWayTlsConfig { // 建议通过配置文件注入参数,避免硬编码 @Value("${feign.client.tls.keystore-path}") private String keystorePath; @Value("${feign.client.tls.keystore-password}") private String keystorePassword; @Value("${feign.client.tls.key-password}") private String keyPassword; @Value("${feign.client.tls.truststore-path}") private String truststorePath; @Value("${feign.client.tls.truststore-password}") private String truststorePassword; @Bean public SSLConnectionSocketFactory sslConnectionSocketFactory() throws Exception { KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); try (InputStream is = new FileInputStream(keystorePath)) { keyStore.load(is, keystorePassword.toCharArray()); } KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); try (InputStream is = new FileInputStream(truststorePath)) { trustStore.load(is, truststorePassword.toCharArray()); } SSLContext sslContext = SSLContexts.custom() .loadKeyMaterial(keyStore, keyPassword.toCharArray()) .loadTrustMaterial(trustStore, null) .build(); return new SSLConnectionSocketFactory( sslContext, new String[]{"TLSv1.2"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier()); } @Bean public HttpClientConnectionManager connectionManager(SSLConnectionSocketFactory sslSocketFactory) { Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create() .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", sslSocketFactory) .build(); PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(registry); manager.setMaxTotal(100); manager.setDefaultMaxPerRoute(10); return manager; } @Bean public CloseableHttpClient httpClient(HttpClientConnectionManager connectionManager) { return HttpClients.custom() .setConnectionManager(connectionManager) .build(); } @Bean public Client feignClient(HttpClient httpClient) { return new ApacheHttpClient(httpClient); } }
在Feign客户端上指定配置
@FeignClient(name = "your-target-service", configuration = FeignTwoWayTlsConfig.class) public interface YourFeignClient { // 定义你的API接口方法 }
双向TLS的关键注意事项
- 确保客户端密钥库包含有效的客户端证书,且服务端的信任库已经导入该客户端证书(双向TLS需要双方互相验证)
- 密钥库和信任库的格式(JKS/PKCS12)要匹配,密码不能错误
- 强制指定TLSv1.2(或更高版本),避免使用不安全的TLS协议版本
内容的提问来源于stack exchange,提问作者Spork
相关产品推荐
相关产品推荐

