如何覆盖Spring Cloud Eureka默认发现客户端默认ssl上下文?

时间:2017-02-17 04:04:22

标签: java spring ssl spring-cloud netflix-eureka

我试图为spring cloud eureka服务器启用https。 Yaml配置:

server:
  port: 8100
ssl:
  clientAuth: want
  protocol: TLS
  key-store: classpath:keystore/keystore.jks
  key-store-password: some
  key-password: some
eureka:
  instance:
    prefer-ip-address: true
    non-secure-port-enabled: false
    secure-port-enabled: true
    secure-port: ${server.port}
    healthCheckUrl: https://${eureka.hostname}:${secure-port}/health
    statusPageUrl: https://${eureka.hostname}:${secure-port}/info
    homePageUrl: https://${eureka.hostname}:${secure-port}/
security:
  basic:
    enabled: true

然后我启动一个客户端注册到服务器。我没有导入自签名证书,因此我得到了解雇 sun.security.provider.certpath.SunCertPathBuilderException:无法找到所请求目标的有效证书路径。我不想导入证书,因为有大量的实例和证书管理成本很高。因此,我将cert放在classpath中并在启动时加载它。我通过添加代码

覆盖客户端默认的ssl上下文和ssl socket facotory
SSLContext.setDefault(sslContext);
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());

当使用假装调用其他eureka客户端时,它工作正常。但是,在注册到eureka服务器时它没有任何努力。 我检查了源代码,发现eureka发现客户端使用jersey,jersey调用http apache客户端。 问题是,它使用 SchemeRegistryFactory.createDefault(),它将最终调用 SSLContexts.createDefault(),他们不会考虑系统属性。换句话说,这个http客户端不会标记我的自定义SSLContexts。 所以我的问题是,有一种方法可以在eureka发现客户端中添加/重新定位/替换默认的http客户端吗?

2 个答案:

答案 0 :(得分:6)

最后,我在多次挖掘源代码后找到了解决方案。我正在使用版本Camden.SR5,它将调用eureka-client-1.4.12。

如果您在 DiscoveryClientOptionalArgs 中提供 EurekaJerseyClient ,则Discovery客户端将不会初始化默认客户端。类 DiscoveryClient

中的部分代码
enum TopBarStyle: Bool {
    case darkOnLight
    case lightOnDark
}

@IBInspectable var style = TopBarStyle(rawValue: false)!

然后我添加一个类来创建 DiscoveryClientOptionalArgs bean。

private void scheduleServerEndpointTask(EurekaTransport eurekaTransport,
                                        DiscoveryClientOptionalArgs args) {
...

    EurekaJerseyClient providedJerseyClient = args == null
            ? null
            : args.eurekaJerseyClient;

    eurekaTransport.transportClientFactory = providedJerseyClient == null
            ? TransportClientFactories.newTransportClientFactory(clientConfig, additionalFilters, applicationInfoManager.getInfo())
            : TransportClientFactories.newTransportClientFactory(additionalFilters, providedJerseyClient);
...
}

}

为确保我的自定义 EurekaJerseyClient 运行良好,我从 EurekaJerseyClientImpl 分叉代码并进行了一些修改。

import com.netflix.discovery.DiscoveryClient;
import com.netflix.discovery.EurekaClientConfig;
import com.netflix.discovery.converters.wrappers.CodecWrappers;
import com.netflix.discovery.shared.transport.jersey.EurekaJerseyClient;
import com.qy.insurance.cloud.core.eureka.CustomEurekaJerseyClientBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@Slf4j
public class EurekaSslConfig {

@Value("${eureka.client.service-url.defaultZone}")
private String defaultZone;

@Autowired
private EurekaClientConfig config;

@Autowired
private DefaultSslConfig defaultSslConfig;

@Bean
public DiscoveryClient.DiscoveryClientOptionalArgs discoveryClientOptionalArgs(){
    if(!defaultSslConfig.isFinish()){
        log.warn("Default SSLContext might not have been updated! Please check!");
    }

    DiscoveryClient.DiscoveryClientOptionalArgs args = new DiscoveryClient.DiscoveryClientOptionalArgs();

    CustomEurekaJerseyClientBuilder clientBuilder = new CustomEurekaJerseyClientBuilder()
            .withClientName("DiscoveryClient-HTTPClient-Custom")
            .withUserAgent("Java-EurekaClient")
            .withConnectionTimeout(config.getEurekaServerConnectTimeoutSeconds() * 1000)
            .withReadTimeout(config.getEurekaServerReadTimeoutSeconds() * 1000)
            .withMaxConnectionsPerHost(config.getEurekaServerTotalConnectionsPerHost())
            .withMaxTotalConnections(config.getEurekaServerTotalConnections())
            .withConnectionIdleTimeout(config.getEurekaConnectionIdleTimeoutSeconds() * 1000)
            .withEncoderWrapper(CodecWrappers.getEncoder(config.getEncoderName()))
            .withDecoderWrapper(CodecWrappers.resolveDecoder(config.getDecoderName(), config.getClientDataAccept()));
    if (defaultZone.startsWith("https://")) {
        clientBuilder.withSystemSSLConfiguration();
    }

    EurekaJerseyClient jerseyClient = clientBuilder.build();
    args.setEurekaJerseyClient(jerseyClient);//Provide custom EurekaJerseyClient to override default one
    return args;
}

希望这可以帮助那些不像我一样在生产JVM中导入证书的人。

答案 1 :(得分:0)

我设法在Finchley.M9 spring cloud中向eureka客户端注入了ssl-context,如下所示:

@Configuration
public class SslConfiguration {

    private static final Logger logger = LoggerFactory.getLogger(LoggerConfiguration.class);

    @Value("${http.client.ssl.trust-store}")
    private File trustStore;
    @Value("${http.client.ssl.trust-store-password}")
    private String trustStorePassword;


    @Bean
    public DiscoveryClient.DiscoveryClientOptionalArgs getTrustStoredEurekaClient(SSLContext sslContext) {
        DiscoveryClient.DiscoveryClientOptionalArgs args = new DiscoveryClient.DiscoveryClientOptionalArgs();
        args.setSSLContext(sslContext);
        return args;
    }

    @Bean
    public SSLContext sslContext() throws Exception {
        logger.info("initialize ssl context bean with keystore {} ", trustStore);
        return new SSLContextBuilder()
                .loadTrustMaterial(
                        trustStore,
                        trustStorePassword.toCharArray()
                ).build();
    }
}