mirror of
https://github.com/spring-cloud/spring-cloud-netflix.git
synced 2026-09-17 15:49:00 +00:00
Added RestTemplate TLS support
fixes gh-3935
This commit is contained in:
@@ -107,6 +107,8 @@ The `eureka.client.tls.enabled` needs to be true to enable Eureka client side TL
|
||||
|
||||
NOTE: Because of a limitation in Eureka, it is not possible to support per-server basic auth credentials, so only the first set that are found is used.
|
||||
|
||||
If you want to customize the RestTemplate used by the Eureka HTTP Client you may want to create a bean of `EurekaClientHttpRequestFactorySupplier` and provide your own logic for generating a `ClientHttpRequestFactory` instance.
|
||||
|
||||
=== Status Page and Health Indicator
|
||||
|
||||
The status page and health indicators for a Eureka instance default to `/info` and `/health` respectively, which are the default locations of useful endpoints in a Spring Boot Actuator application.
|
||||
|
||||
@@ -100,4 +100,21 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>**/*Suite.java</include>
|
||||
</includes>
|
||||
<excludes>
|
||||
<exclude>**/*Test.java</exclude>
|
||||
<exclude>**/*Tests.java</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
+11
@@ -46,6 +46,7 @@ public class AppRunner implements AutoCloseable {
|
||||
public void start() {
|
||||
if (app == null) {
|
||||
SpringApplicationBuilder builder = new SpringApplicationBuilder(appClass);
|
||||
builder.properties("spring.application.name=" + appClass.getName());
|
||||
builder.properties("spring.jmx.enabled=false");
|
||||
builder.properties(String.format("server.port=%d", availabeTcpPort()));
|
||||
builder.properties(props());
|
||||
@@ -72,6 +73,16 @@ public class AppRunner implements AutoCloseable {
|
||||
public void stop() {
|
||||
if (app != null) {
|
||||
app.stop();
|
||||
int attempts = 5;
|
||||
while (app.isRunning() && attempts > 0) {
|
||||
attempts = attempts - 1;
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
app = null;
|
||||
}
|
||||
}
|
||||
|
||||
+110
@@ -20,11 +20,22 @@ import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.KeyStore;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public abstract class BaseCertTest {
|
||||
|
||||
private static final Log log = LogFactory.getLog(BaseCertTest.class);
|
||||
|
||||
protected static final String KEY_STORE_PASSWORD = "test-key-store-password";
|
||||
|
||||
protected static final String KEY_PASSWORD = "test-key-password";
|
||||
@@ -44,6 +55,45 @@ public abstract class BaseCertTest {
|
||||
protected BaseCertTest() {
|
||||
}
|
||||
|
||||
static EurekaServerRunner startEurekaServer(Class config) {
|
||||
EurekaServerRunner server = new EurekaServerRunner(config);
|
||||
server.enableTls();
|
||||
server.setKeyStore(serverCert, KEY_STORE_PASSWORD, "server", KEY_PASSWORD);
|
||||
server.setTrustStore(caCert, KEY_STORE_PASSWORD);
|
||||
|
||||
server.start();
|
||||
return server;
|
||||
}
|
||||
|
||||
static void stopEurekaServer(EurekaServerRunner server) {
|
||||
server.stop();
|
||||
}
|
||||
|
||||
static EurekaClientRunner startService(EurekaServerRunner server, Class config) {
|
||||
EurekaClientRunner service = new EurekaClientRunner(config, server, "testservice");
|
||||
enableTlsClient(service);
|
||||
service.start();
|
||||
return service;
|
||||
}
|
||||
|
||||
static void stopService(EurekaClientRunner service) {
|
||||
service.stop();
|
||||
}
|
||||
|
||||
static void enableTlsClient(EurekaClientRunner runner) {
|
||||
runner.enableTls();
|
||||
runner.setKeyStore(clientCert, KEY_STORE_PASSWORD, KEY_PASSWORD);
|
||||
runner.setTrustStore(caCert, KEY_STORE_PASSWORD);
|
||||
}
|
||||
|
||||
static void waitForRegistration(Supplier<EurekaClientRunner> clientSupplier) {
|
||||
try (EurekaClientRunner client = clientSupplier.get()) {
|
||||
enableTlsClient(client);
|
||||
client.start();
|
||||
client.waitServiceViaEureka(60);
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void createCertificates() throws Exception {
|
||||
KeyTool tool = new KeyTool();
|
||||
@@ -63,6 +113,66 @@ public abstract class BaseCertTest {
|
||||
wrongClientCert = saveKeyAndCert(wrongClient);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() {
|
||||
log.info("Tests finished!");
|
||||
}
|
||||
|
||||
abstract EurekaClientRunner createEurekaClient();
|
||||
|
||||
/**
|
||||
* Already proved this in waitForRegistration(). Keep this Test to express test
|
||||
* purpose explicitly.
|
||||
*/
|
||||
@Test
|
||||
public void clientCertCanWork() {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noCertCannotWork() {
|
||||
try (EurekaClientRunner client = createEurekaClient()) {
|
||||
client.disableTls();
|
||||
client.start();
|
||||
assertThat(client.foundServiceViaEureka()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wrongCertCannotWork() {
|
||||
try (EurekaClientRunner client = createEurekaClient()) {
|
||||
enableTlsClient(client);
|
||||
client.setKeyStore(wrongClientCert);
|
||||
client.start();
|
||||
assertThat(client.foundServiceViaEureka()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = BeanCreationException.class)
|
||||
public void wrongPasswordCauseFailure() {
|
||||
EurekaClientRunner client = createEurekaClient();
|
||||
enableTlsClient(client);
|
||||
client.setKeyStore(clientCert, WRONG_PASSWORD, WRONG_PASSWORD);
|
||||
client.start();
|
||||
}
|
||||
|
||||
@Test(expected = BeanCreationException.class)
|
||||
public void nonExistKeyStoreCauseFailure() {
|
||||
EurekaClientRunner client = createEurekaClient();
|
||||
enableTlsClient(client);
|
||||
client.setKeyStore(new File("nonExistFile"));
|
||||
client.start();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wrongTrustStoreCannotWork() {
|
||||
try (EurekaClientRunner client = createEurekaClient()) {
|
||||
enableTlsClient(client);
|
||||
client.setTrustStore(wrongCaCert);
|
||||
client.start();
|
||||
assertThat(client.foundServiceViaEureka()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
private static File saveKeyAndCert(KeyAndCert keyCert) throws Exception {
|
||||
return saveKeyStore(keyCert.subject(), () -> keyCert.storeKeyAndCert(KEY_PASSWORD));
|
||||
}
|
||||
|
||||
+6
@@ -19,6 +19,8 @@ package org.springframework.cloud.netflix.eureka;
|
||||
import java.io.File;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs;
|
||||
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
|
||||
public class EurekaClientRunner extends AppRunner {
|
||||
@@ -96,4 +98,8 @@ public class EurekaClientRunner extends AppRunner {
|
||||
return !discovery.getServices().isEmpty();
|
||||
}
|
||||
|
||||
public AbstractDiscoveryClientOptionalArgs<Void> discoveryClientOptionalArgs() {
|
||||
return this.getBean(AbstractDiscoveryClientOptionalArgs.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2018-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.eureka;
|
||||
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Suite;
|
||||
|
||||
/**
|
||||
* We need to use a suite cause we need to first run all the Eureka Servers, then close
|
||||
* all of them. We can't run one EurekaServer, close it and then run another one, cause
|
||||
* Eureka is using static executor services that are shutdown when we close a context.
|
||||
* That means that when the new context starts we will fail cause the executor service is
|
||||
* already shutdown.
|
||||
*/
|
||||
@RunWith(Suite.class)
|
||||
@Suite.SuiteClasses({ EurekaClientTest.class, RestTemplateEurekaClientTest.class })
|
||||
public class EurekaClientSuite {
|
||||
|
||||
}
|
||||
+16
-107
@@ -16,13 +16,11 @@
|
||||
|
||||
package org.springframework.cloud.netflix.eureka;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import com.netflix.discovery.DiscoveryClient;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
|
||||
@@ -31,117 +29,28 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class EurekaClientTest extends BaseCertTest {
|
||||
|
||||
private static EurekaServerRunner server;
|
||||
private static final Log log = LogFactory.getLog(EurekaClientTest.class);
|
||||
|
||||
private static EurekaClientRunner service;
|
||||
static EurekaServerRunner server;
|
||||
|
||||
static EurekaClientRunner service;
|
||||
|
||||
@BeforeClass
|
||||
public static void setupAll() {
|
||||
startEurekaServer();
|
||||
startService();
|
||||
waitForRegistration();
|
||||
server = startEurekaServer(EurekaClientTest.TestEurekaServer.class);
|
||||
service = startService(server, EurekaClientTest.TestApp.class);
|
||||
// Will use Jersey
|
||||
assertThat(service.discoveryClientOptionalArgs())
|
||||
.isInstanceOf(DiscoveryClient.DiscoveryClientOptionalArgs.class);
|
||||
log.info("Successfully asserted that Jersey will be used");
|
||||
waitForRegistration(() -> new EurekaClientTest().createEurekaClient());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownAll() {
|
||||
stopService();
|
||||
stopEurekaServer();
|
||||
}
|
||||
|
||||
private static void startEurekaServer() {
|
||||
server = new EurekaServerRunner(TestEurekaServer.class);
|
||||
server.enableTls();
|
||||
server.setKeyStore(serverCert, KEY_STORE_PASSWORD, "server", KEY_PASSWORD);
|
||||
server.setTrustStore(caCert, KEY_STORE_PASSWORD);
|
||||
|
||||
server.start();
|
||||
}
|
||||
|
||||
private static void stopEurekaServer() {
|
||||
server.stop();
|
||||
}
|
||||
|
||||
private static void startService() {
|
||||
service = new EurekaClientRunner(TestApp.class, server, "testservice");
|
||||
enableTlsClient(service);
|
||||
service.start();
|
||||
}
|
||||
|
||||
private static void stopService() {
|
||||
service.stop();
|
||||
}
|
||||
|
||||
private static void waitForRegistration() {
|
||||
try (EurekaClientRunner client = createEurekaClient()) {
|
||||
enableTlsClient(client);
|
||||
client.start();
|
||||
client.waitServiceViaEureka(60);
|
||||
}
|
||||
}
|
||||
|
||||
private static EurekaClientRunner createEurekaClient() {
|
||||
@Override
|
||||
EurekaClientRunner createEurekaClient() {
|
||||
return new EurekaClientRunner(TestApp.class, server);
|
||||
}
|
||||
|
||||
private static void enableTlsClient(EurekaClientRunner runner) {
|
||||
runner.enableTls();
|
||||
runner.setKeyStore(clientCert, KEY_STORE_PASSWORD, KEY_PASSWORD);
|
||||
runner.setTrustStore(caCert, KEY_STORE_PASSWORD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Already proved this in waitForRegistration(). Keep this Test to express test
|
||||
* purpose explicitly.
|
||||
*/
|
||||
@Test
|
||||
public void clientCertCanWork() {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noCertCannotWork() {
|
||||
try (EurekaClientRunner client = createEurekaClient()) {
|
||||
client.disableTls();
|
||||
client.start();
|
||||
assertThat(client.foundServiceViaEureka()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wrongCertCannotWork() {
|
||||
try (EurekaClientRunner client = createEurekaClient()) {
|
||||
enableTlsClient(client);
|
||||
client.setKeyStore(wrongClientCert);
|
||||
client.start();
|
||||
assertThat(client.foundServiceViaEureka()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = BeanCreationException.class)
|
||||
public void wrongPasswordCauseFailure() {
|
||||
EurekaClientRunner client = createEurekaClient();
|
||||
enableTlsClient(client);
|
||||
client.setKeyStore(clientCert, WRONG_PASSWORD, WRONG_PASSWORD);
|
||||
client.start();
|
||||
}
|
||||
|
||||
@Test(expected = BeanCreationException.class)
|
||||
public void nonExistKeyStoreCauseFailure() {
|
||||
EurekaClientRunner client = createEurekaClient();
|
||||
enableTlsClient(client);
|
||||
client.setKeyStore(new File("nonExistFile"));
|
||||
client.start();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wrongTrustStoreCannotWork() {
|
||||
try (EurekaClientRunner client = createEurekaClient()) {
|
||||
enableTlsClient(client);
|
||||
client.setTrustStore(wrongCaCert);
|
||||
client.start();
|
||||
assertThat(client.foundServiceViaEureka()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
public static class TestApp {
|
||||
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2018-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.eureka;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.cloud.configuration.TlsProperties;
|
||||
import org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration;
|
||||
import org.springframework.cloud.netflix.eureka.http.EurekaClientHttpRequestFactorySupplier;
|
||||
import org.springframework.cloud.netflix.eureka.http.RestTemplateDiscoveryClientOptionalArgs;
|
||||
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class RestTemplateEurekaClientTest extends BaseCertTest {
|
||||
|
||||
private static final Log log = LogFactory.getLog(RestTemplateEurekaClientTest.class);
|
||||
|
||||
private static EurekaServerRunner server;
|
||||
|
||||
private static EurekaClientRunner service;
|
||||
|
||||
@BeforeClass
|
||||
public static void setupAll() {
|
||||
server = startEurekaServer(RestTemplateEurekaClientTest.RestTemplateTestEurekaServer.class);
|
||||
service = startService(server, RestTemplateEurekaClientTest.RestTemplateTestApp.class);
|
||||
// Will use RestTemplate
|
||||
assertThat(service.discoveryClientOptionalArgs()).isInstanceOf(RestTemplateDiscoveryClientOptionalArgs.class);
|
||||
log.info("Successfully asserted that RestTemplate will be used");
|
||||
waitForRegistration(() -> new RestTemplateEurekaClientTest().createEurekaClient());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownAll() {
|
||||
stopService(service);
|
||||
stopEurekaServer(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
EurekaClientRunner createEurekaClient() {
|
||||
return new EurekaClientRunner(RestTemplateTestApp.class, server);
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
public static class RestTemplateTestApp {
|
||||
|
||||
// Want to force reusing exactly the same bean as on production without excluding
|
||||
// jersey from the classpath
|
||||
@Bean
|
||||
public RestTemplateDiscoveryClientOptionalArgs forceRestTemplateDiscoveryClientOptionalArgs(
|
||||
TlsProperties tlsProperties, DiscoveryClientOptionalArgsConfiguration configuration,
|
||||
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier)
|
||||
throws GeneralSecurityException, IOException {
|
||||
return configuration.restTemplateDiscoveryClientOptionalArgs(tlsProperties,
|
||||
eurekaClientHttpRequestFactorySupplier);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
@EnableEurekaServer
|
||||
public static class RestTemplateTestEurekaServer {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+12
-2
@@ -34,6 +34,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.configuration.SSLContextFactory;
|
||||
import org.springframework.cloud.configuration.TlsProperties;
|
||||
import org.springframework.cloud.netflix.eureka.MutableDiscoveryClientOptionalArgs;
|
||||
import org.springframework.cloud.netflix.eureka.http.DefaultEurekaClientHttpRequestFactorySupplier;
|
||||
import org.springframework.cloud.netflix.eureka.http.EurekaClientHttpRequestFactorySupplier;
|
||||
import org.springframework.cloud.netflix.eureka.http.RestTemplateDiscoveryClientOptionalArgs;
|
||||
import org.springframework.cloud.netflix.eureka.http.WebClientDiscoveryClientOptionalArgs;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -60,14 +62,22 @@ public class DiscoveryClientOptionalArgsConfiguration {
|
||||
@ConditionalOnMissingBean(value = { AbstractDiscoveryClientOptionalArgs.class }, search = SearchStrategy.CURRENT)
|
||||
@ConditionalOnProperty(prefix = "eureka.client", name = "webclient.enabled", matchIfMissing = true,
|
||||
havingValue = "false")
|
||||
public RestTemplateDiscoveryClientOptionalArgs restTemplateDiscoveryClientOptionalArgs(TlsProperties tlsProperties)
|
||||
public RestTemplateDiscoveryClientOptionalArgs restTemplateDiscoveryClientOptionalArgs(TlsProperties tlsProperties,
|
||||
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier)
|
||||
throws GeneralSecurityException, IOException {
|
||||
logger.info("Eureka HTTP Client uses RestTemplate.");
|
||||
RestTemplateDiscoveryClientOptionalArgs result = new RestTemplateDiscoveryClientOptionalArgs();
|
||||
RestTemplateDiscoveryClientOptionalArgs result = new RestTemplateDiscoveryClientOptionalArgs(
|
||||
eurekaClientHttpRequestFactorySupplier);
|
||||
setupTLS(result, tlsProperties);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
EurekaClientHttpRequestFactorySupplier defaultEurekaClientHttpRequestFactorySupplier() {
|
||||
return new DefaultEurekaClientHttpRequestFactorySupplier();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(name = "com.sun.jersey.api.client.filter.ClientFilter")
|
||||
@ConditionalOnMissingBean(value = AbstractDiscoveryClientOptionalArgs.class, search = SearchStrategy.CURRENT)
|
||||
|
||||
+15
-3
@@ -30,7 +30,10 @@ import org.springframework.boot.autoconfigure.web.reactive.function.client.WebCl
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.config.client.ConfigServerInstanceProvider;
|
||||
import org.springframework.cloud.config.client.ConfigServicePropertySourceLocator;
|
||||
import org.springframework.cloud.configuration.TlsProperties;
|
||||
import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean;
|
||||
import org.springframework.cloud.netflix.eureka.http.DefaultEurekaClientHttpRequestFactorySupplier;
|
||||
import org.springframework.cloud.netflix.eureka.http.EurekaClientHttpRequestFactorySupplier;
|
||||
import org.springframework.cloud.netflix.eureka.http.RestTemplateEurekaHttpClient;
|
||||
import org.springframework.cloud.netflix.eureka.http.RestTemplateTransportClientFactory;
|
||||
import org.springframework.cloud.netflix.eureka.http.WebClientEurekaHttpClient;
|
||||
@@ -38,6 +41,7 @@ import org.springframework.cloud.netflix.eureka.http.WebClientTransportClientFac
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
@@ -63,9 +67,17 @@ public class EurekaConfigServerBootstrapConfiguration {
|
||||
@ConditionalOnProperty(prefix = "eureka.client", name = "webclient.enabled", matchIfMissing = true,
|
||||
havingValue = "false")
|
||||
public RestTemplateEurekaHttpClient configDiscoveryRestTemplateEurekaHttpClient(EurekaClientConfigBean config,
|
||||
Environment env) {
|
||||
return (RestTemplateEurekaHttpClient) new RestTemplateTransportClientFactory()
|
||||
.newClient(HostnameBasedUrlRandomizer.randomEndpoint(config, env));
|
||||
Environment env, @Nullable TlsProperties properties,
|
||||
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier) {
|
||||
return (RestTemplateEurekaHttpClient) new RestTemplateTransportClientFactory(properties,
|
||||
eurekaClientHttpRequestFactorySupplier)
|
||||
.newClient(HostnameBasedUrlRandomizer.randomEndpoint(config, env));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
EurekaClientHttpRequestFactorySupplier defaultEurekaClientHttpRequestFactorySupplier() {
|
||||
return new DefaultEurekaClientHttpRequestFactorySupplier();
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
+8
-2
@@ -23,7 +23,10 @@ import org.springframework.boot.Bootstrapper;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.cloud.config.client.ConfigClientProperties;
|
||||
import org.springframework.cloud.config.client.ConfigServerInstanceProvider;
|
||||
import org.springframework.cloud.configuration.TlsProperties;
|
||||
import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean;
|
||||
import org.springframework.cloud.netflix.eureka.http.DefaultEurekaClientHttpRequestFactorySupplier;
|
||||
import org.springframework.cloud.netflix.eureka.http.EurekaClientHttpRequestFactorySupplier;
|
||||
import org.springframework.cloud.netflix.eureka.http.RestTemplateTransportClientFactory;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
@@ -50,8 +53,11 @@ public class EurekaConfigServerBootstrapper implements Bootstrapper {
|
||||
return null;
|
||||
}
|
||||
EurekaClientConfigBean config = context.get(EurekaClientConfigBean.class);
|
||||
EurekaHttpClient httpClient = new RestTemplateTransportClientFactory()
|
||||
.newClient(HostnameBasedUrlRandomizer.randomEndpoint(config, binder));
|
||||
EurekaHttpClient httpClient = new RestTemplateTransportClientFactory(
|
||||
context.getOrElse(TlsProperties.class, null),
|
||||
context.getOrElse(EurekaClientHttpRequestFactorySupplier.class,
|
||||
new DefaultEurekaClientHttpRequestFactorySupplier()))
|
||||
.newClient(HostnameBasedUrlRandomizer.randomEndpoint(config, binder));
|
||||
return new EurekaConfigServerInstanceProvider(httpClient, config)::getInstances;
|
||||
});
|
||||
}
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.eureka.http;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Supplier for the {@link ClientHttpRequestFactory} to be used by Eureka client that uses
|
||||
* {@link HttpClients}.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public class DefaultEurekaClientHttpRequestFactorySupplier implements EurekaClientHttpRequestFactorySupplier {
|
||||
|
||||
@Override
|
||||
public ClientHttpRequestFactory get(SSLContext sslContext, @Nullable HostnameVerifier hostnameVerifier) {
|
||||
HttpClientBuilder httpClientBuilder = HttpClients.custom().setSSLContext(sslContext);
|
||||
if (hostnameVerifier != null) {
|
||||
httpClientBuilder = httpClientBuilder.setSSLHostnameVerifier(hostnameVerifier);
|
||||
}
|
||||
CloseableHttpClient httpClient = httpClientBuilder.build();
|
||||
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
|
||||
requestFactory.setHttpClient(httpClient);
|
||||
return requestFactory;
|
||||
}
|
||||
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.eureka.http;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Supplier for the {@link ClientHttpRequestFactory} to be used by Eureka client.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public interface EurekaClientHttpRequestFactorySupplier {
|
||||
|
||||
/**
|
||||
* Returns a constructed {@link ClientHttpRequestFactory}.
|
||||
* @param sslContext SSL Context
|
||||
* @param hostnameVerifier Hostname verifier
|
||||
* @return {@link ClientHttpRequestFactory}
|
||||
*/
|
||||
ClientHttpRequestFactory get(SSLContext sslContext, @Nullable HostnameVerifier hostnameVerifier);
|
||||
|
||||
}
|
||||
+14
-1
@@ -23,8 +23,21 @@ import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs;
|
||||
*/
|
||||
public class RestTemplateDiscoveryClientOptionalArgs extends AbstractDiscoveryClientOptionalArgs<Void> {
|
||||
|
||||
protected final EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier;
|
||||
|
||||
public RestTemplateDiscoveryClientOptionalArgs(
|
||||
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier) {
|
||||
this.eurekaClientHttpRequestFactorySupplier = eurekaClientHttpRequestFactorySupplier;
|
||||
setTransportClientFactories(new RestTemplateTransportClientFactories(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated - use
|
||||
* {@link RestTemplateDiscoveryClientOptionalArgs#RestTemplateDiscoveryClientOptionalArgs(EurekaClientHttpRequestFactorySupplier)}
|
||||
*/
|
||||
@Deprecated
|
||||
public RestTemplateDiscoveryClientOptionalArgs() {
|
||||
setTransportClientFactories(new RestTemplateTransportClientFactories());
|
||||
this(new DefaultEurekaClientHttpRequestFactorySupplier());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+10
-2
@@ -33,6 +33,12 @@ import com.netflix.discovery.shared.transport.jersey.TransportClientFactories;
|
||||
*/
|
||||
public class RestTemplateTransportClientFactories implements TransportClientFactories<Void> {
|
||||
|
||||
private final RestTemplateDiscoveryClientOptionalArgs args;
|
||||
|
||||
public RestTemplateTransportClientFactories(RestTemplateDiscoveryClientOptionalArgs args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportClientFactory newTransportClientFactory(Collection<Void> additionalFilters,
|
||||
EurekaJerseyClient providedJerseyClient) {
|
||||
@@ -42,14 +48,16 @@ public class RestTemplateTransportClientFactories implements TransportClientFact
|
||||
@Override
|
||||
public TransportClientFactory newTransportClientFactory(EurekaClientConfig clientConfig,
|
||||
Collection<Void> additionalFilters, InstanceInfo myInstanceInfo) {
|
||||
return new RestTemplateTransportClientFactory();
|
||||
return new RestTemplateTransportClientFactory(this.args.getSSLContext(), this.args.getHostnameVerifier(),
|
||||
this.args.eurekaClientHttpRequestFactorySupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportClientFactory newTransportClientFactory(final EurekaClientConfig clientConfig,
|
||||
final Collection<Void> additionalFilters, final InstanceInfo myInstanceInfo,
|
||||
final Optional<SSLContext> sslContext, final Optional<HostnameVerifier> hostnameVerifier) {
|
||||
return new RestTemplateTransportClientFactory();
|
||||
return new RestTemplateTransportClientFactory(this.args.getSSLContext(), this.args.getHostnameVerifier(),
|
||||
this.args.eurekaClientHttpRequestFactorySupplier);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+58
-1
@@ -18,6 +18,10 @@ package org.springframework.cloud.netflix.eureka.http;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import com.fasterxml.jackson.databind.BeanDescription;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
@@ -38,7 +42,10 @@ import com.netflix.discovery.shared.resolver.EurekaEndpoint;
|
||||
import com.netflix.discovery.shared.transport.EurekaHttpClient;
|
||||
import com.netflix.discovery.shared.transport.TransportClientFactory;
|
||||
|
||||
import org.springframework.cloud.configuration.SSLContextFactory;
|
||||
import org.springframework.cloud.configuration.TlsProperties;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.support.BasicAuthenticationInterceptor;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.web.client.DefaultResponseErrorHandler;
|
||||
@@ -53,13 +60,53 @@ import org.springframework.web.client.RestTemplate;
|
||||
*/
|
||||
public class RestTemplateTransportClientFactory implements TransportClientFactory {
|
||||
|
||||
private final Optional<SSLContext> sslContext;
|
||||
|
||||
private final Optional<HostnameVerifier> hostnameVerifier;
|
||||
|
||||
private final EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier;
|
||||
|
||||
public RestTemplateTransportClientFactory(TlsProperties tlsProperties,
|
||||
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier) {
|
||||
this.sslContext = context(tlsProperties);
|
||||
this.hostnameVerifier = Optional.empty();
|
||||
this.eurekaClientHttpRequestFactorySupplier = eurekaClientHttpRequestFactorySupplier;
|
||||
}
|
||||
|
||||
private Optional<SSLContext> context(TlsProperties properties) {
|
||||
if (properties == null || !properties.isEnabled()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
try {
|
||||
return Optional.of(new SSLContextFactory(properties).createSSLContext());
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public RestTemplateTransportClientFactory(Optional<SSLContext> sslContext,
|
||||
Optional<HostnameVerifier> hostnameVerifier,
|
||||
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier) {
|
||||
this.sslContext = sslContext;
|
||||
this.hostnameVerifier = hostnameVerifier;
|
||||
this.eurekaClientHttpRequestFactorySupplier = eurekaClientHttpRequestFactorySupplier;
|
||||
}
|
||||
|
||||
public RestTemplateTransportClientFactory() {
|
||||
this.sslContext = Optional.empty();
|
||||
this.hostnameVerifier = Optional.empty();
|
||||
this.eurekaClientHttpRequestFactorySupplier = new DefaultEurekaClientHttpRequestFactorySupplier();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EurekaHttpClient newClient(EurekaEndpoint serviceUrl) {
|
||||
return new RestTemplateEurekaHttpClient(restTemplate(serviceUrl.getServiceUrl()), serviceUrl.getServiceUrl());
|
||||
}
|
||||
|
||||
private RestTemplate restTemplate(String serviceUrl) {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
RestTemplate restTemplate = restTemplate();
|
||||
|
||||
try {
|
||||
URI serviceURI = new URI(serviceUrl);
|
||||
if (serviceURI.getUserInfo() != null) {
|
||||
@@ -80,6 +127,16 @@ public class RestTemplateTransportClientFactory implements TransportClientFactor
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
private RestTemplate restTemplate() {
|
||||
if (this.sslContext.isPresent()) {
|
||||
SSLContext sslContext = this.sslContext.get();
|
||||
ClientHttpRequestFactory requestFactory = this.eurekaClientHttpRequestFactorySupplier.get(sslContext,
|
||||
this.hostnameVerifier.orElse(null));
|
||||
return new RestTemplate(requestFactory);
|
||||
}
|
||||
return new RestTemplate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the serialization configurations required by the Eureka Server. JSON
|
||||
* content exchanged with eureka requires a root node matching the entity being
|
||||
|
||||
+2
-1
@@ -25,7 +25,8 @@ public class RestTemplateTransportClientFactoriesTest {
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testJerseyIsUnsuported() {
|
||||
new RestTemplateTransportClientFactories().newTransportClientFactory(null, null);
|
||||
new RestTemplateTransportClientFactories(new RestTemplateDiscoveryClientOptionalArgs(null))
|
||||
.newTransportClientFactory(null, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user