Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions docs/modules/ROOT/pages/spring-cloud-openfeign.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,31 @@ Apache HttpClient 5 uses a connection pool to reuse persistent HTTP connections

|===

To apply a Spring Boot SSL bundle to the auto-configured Apache HttpClient 5 connection manager, set `spring.cloud.openfeign.httpclient.hc5.ssl-bundle` to the bundle name:

[source,yaml]
----
spring:
ssl:
bundle:
jks:
secure-service:
truststore:
location: classpath:truststore.p12
password: ${TRUSTSTORE_PASSWORD}
cloud:
openfeign:
httpclient:
hc5:
ssl-bundle: secure-service
----

The bundle supplies key and trust material, and any configured protocols and ciphers; hostname verification remains enabled.
It cannot be combined with `spring.cloud.openfeign.httpclient.disable-ssl-validation=true`.
This setting applies to the shared Apache HttpClient 5 connection manager, not to individual Feign clients or other transports.
A custom `CloseableHttpClient` or `HttpClientConnectionManager` bean retains control of its own SSL configuration, and connection manager customizers are applied after the bundle settings.
The bundle is applied when the connection manager is created; automatic reload of SSL bundles is not supported.

If you can not configure Apache HttpClient 5 by using properties, there is an `HttpClient5FeignConfiguration.HttpClientBuilderCustomizer` interface for programmatic configuration. Similarly, to configure the `HttpClientConnectionManager`, you can use `HttpClient5FeignConfiguration.HttpClientConnectionManagerBuilderCustomizer`. Both usages are shown in the example below.

TIP: Apache HTTP Components `5.4` have changed defaults in the HttpClient relating to HTTP/1.1 TLS upgrades. Most proxy servers handle upgrades without issue, however, you may encounter issues with Envoy or Istio. If you need to restore previous behaviour, you can use `HttpClient5FeignConfiguration.HttpClientBuilderCustomizer` to do it, as shown in the example below.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,24 @@
import org.apache.hc.core5.util.Timeout;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.boot.ssl.SslOptions;
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
* Default configuration for {@link CloseableHttpClient}.
*
* @author Nguyen Ky Thanh
* @author changjin wei(魏昌进)
* @author Kwangyong Kim
* @author Goutam Adwant
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CloseableHttpClient.class)
Expand All @@ -69,13 +76,16 @@ public class HttpClient5FeignConfiguration {

private CloseableHttpClient httpClient5;

@Autowired
private ObjectProvider<SslBundles> sslBundlesProvider;

@Bean
@ConditionalOnMissingBean(HttpClientConnectionManager.class)
public HttpClientConnectionManager hc5ConnectionManager(FeignHttpClientProperties httpClientProperties,
ObjectProvider<List<HttpClientConnectionManagerBuilderCustomizer>> customizerProvider) {
PoolingHttpClientConnectionManagerBuilder httpClientConnectionManager = PoolingHttpClientConnectionManagerBuilder
.create()
.setSSLSocketFactory(httpsSSLConnectionSocketFactory(httpClientProperties.isDisableSslValidation()))
.setSSLSocketFactory(httpsSSLConnectionSocketFactory(httpClientProperties))
.setMaxConnTotal(httpClientProperties.getMaxConnections())
.setMaxConnPerRoute(httpClientProperties.getMaxConnectionsPerRoute())
.setConnPoolPolicy(PoolReusePolicy.valueOf(httpClientProperties.getHc5().getPoolReusePolicy().name()))
Expand Down Expand Up @@ -122,12 +132,25 @@ public void destroy() {
}
}

private LayeredConnectionSocketFactory httpsSSLConnectionSocketFactory(boolean isDisableSslValidation) {
private LayeredConnectionSocketFactory httpsSSLConnectionSocketFactory(FeignHttpClientProperties properties) {
final SSLConnectionSocketFactoryBuilder sslConnectionSocketFactoryBuilder = SSLConnectionSocketFactoryBuilder
.create()
.setTlsVersions(TLS.V_1_3, TLS.V_1_2);

if (isDisableSslValidation) {
String bundleName = properties.getHc5().getSslBundle();
if (StringUtils.hasText(bundleName)) {
Assert.state(!properties.isDisableSslValidation(),
"An SSL bundle cannot be used with spring.cloud.openfeign.httpclient.disable-ssl-validation=true");
SslBundles sslBundles = sslBundlesProvider.getObject();
SslBundle sslBundle = sslBundles.getBundle(bundleName);
sslConnectionSocketFactoryBuilder.setSslContext(sslBundle.createSslContext());
SslOptions options = sslBundle.getOptions();
if (options.getEnabledProtocols() != null) {
sslConnectionSocketFactoryBuilder.setTlsVersions(options.getEnabledProtocols());
}
sslConnectionSocketFactoryBuilder.setCiphers(options.getCiphers());
}
else if (properties.isDisableSslValidation()) {
try {
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[] { new DisabledValidationTrustManager() }, new SecureRandom());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
* @author Nguyen Ky Thanh
* @author Olga Maciaszek-Sharma
* @author changjin wei(魏昌进)
* @author Goutam Adwant
*/
@ConfigurationProperties(prefix = "spring.cloud.openfeign.httpclient")
public class FeignHttpClientProperties {
Expand Down Expand Up @@ -215,6 +216,12 @@ public static class Hc5Properties {
*/
private PoolConcurrencyPolicy poolConcurrencyPolicy = DEFAULT_POOL_CONCURRENCY_POLICY;

/**
* Name of the SSL bundle to apply to the shared Apache HttpClient 5 connection
* manager. Cannot be combined with disabling SSL validation.
*/
private String sslBundle;

/**
* Pool connection re-use policies.
*/
Expand All @@ -240,6 +247,14 @@ public static class Hc5Properties {
*/
private TimeUnit connectionRequestTimeoutUnit = DEFAULT_CONNECTION_REQUEST_TIMEOUT_UNIT;

public String getSslBundle() {
return sslBundle;
}

public void setSslBundle(String sslBundle) {
this.sslBundle = sslBundle;
}

public PoolConcurrencyPolicy getPoolConcurrencyPolicy() {
return poolConcurrencyPolicy;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.boot.ssl.SslStoreBundle;
import org.springframework.cloud.openfeign.clientconfig.HttpClient5FeignConfiguration.HttpClientBuilderCustomizer;
import org.springframework.cloud.openfeign.clientconfig.HttpClient5FeignConfiguration.HttpClientConnectionManagerBuilderCustomizer;
import org.springframework.context.ConfigurableApplicationContext;
Expand All @@ -44,6 +47,7 @@
* @author Nguyen Ky Thanh
* @author Olga Maciaszek-Sharma
* @author Kwangyong Kim
* @author Goutam Adwant
*/
class FeignHttpClient5ConfigurationTests {

Expand All @@ -56,6 +60,17 @@ private static void verifyHc5BeansAvailable(ConfigurableApplicationContext conte
assertThat(client).isInstanceOf(ApacheHttp5Client.class);
}

@Test
void shouldUseNamedSslBundle() {
try (ConfigurableApplicationContext context = new SpringApplicationBuilder().web(WebApplicationType.NONE)
.properties("spring.cloud.openfeign.httpclient.hc5.ssl-bundle=test")
.sources(FeignAutoConfiguration.class, SslConfig.class)
.run()) {
verifyHc5BeansAvailable(context);
verify(context.getBean(SslBundles.class)).getBundle("test");
}
}

@Test
void shouldInstantiateHttpClient5ByDefaultWhenDependenciesPresent() {
ConfigurableApplicationContext context = new SpringApplicationBuilder().web(WebApplicationType.NONE)
Expand Down Expand Up @@ -118,6 +133,18 @@ void shouldInstantiateHttpClientConnectionManager5ByUsingHttpClientConnectionMan
}
}

@Configuration(proxyBeanMethods = false)
static class SslConfig {

@Bean
SslBundles sslBundles() {
SslBundles bundles = Mockito.mock(SslBundles.class);
Mockito.when(bundles.getBundle("test")).thenReturn(SslBundle.of(SslStoreBundle.of(null, null, null)));
return bundles;
}

}

@Configuration
static class Config {

Expand Down
Loading