Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
325 views
in Technique[技术] by (71.8m points)

java - How to override Spring Cloud Eureka default discovery client default ssl context?

I'm trying to enable https for spring cloud eureka server. Yaml config:

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

Then I start a client to register into the server. I haven't import the self-signed cert thus of cource I get the excpetion sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target. I don't want to import cert because there are plenty instances and certs management cost much. Thus I put the cert in classpath and load it during start up. I override the client default ssl context and ssl socket facotory by adding codes

SSLContext.setDefault(sslContext);
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());

It works fine when it calls other eureka client using feign. However it has no effort when registering to eureka server. I check the source code and find that eureka discovery client using jersey, jersey call the http apache client. The trouble is, it use SchemeRegistryFactory.createDefault() which will final call SSLContexts.createDefault() who will not taken system properties into consideration. In other words, this http client will not token my custom SSLContexts. So my question is, it there a method to add/resigter/replace the default http client in eureka discovery client?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Finally I found a solution after digging the source code many times. I'm using version Camden.SR5 which will call eureka-client-1.4.12.

If you provide a EurekaJerseyClient in DiscoveryClientOptionalArgs, then the Discovery client will not intial the default one. Part of the code from class DiscoveryClient.

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);
...
}

Then I add a class to make a DiscoveryClientOptionalArgs bean.

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;
}

}

To insure my custom EurekaJerseyClient working well, I forked the code from EurekaJerseyClientImpl and made some modification.

import com.netflix.discovery.converters.wrappers.CodecWrappers;
import com.netflix.discovery.converters.wrappers.DecoderWrapper;
import com.netflix.discovery.converters.wrappers.EncoderWrapper;
import com.netflix.discovery.provider.DiscoveryJerseyProvider;
import com.netflix.discovery.shared.MonitoredConnectionManager;
import com.netflix.discovery.shared.transport.jersey.EurekaJerseyClient;
import com.netflix.discovery.shared.transport.jersey.EurekaJerseyClientImpl;
import com.netflix.discovery.shared.transport.jersey.SSLSocketFactoryAdapter;
import com.netflix.discovery.util.DiscoveryBuildInfo;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.client.apache4.config.ApacheHttpClient4Config;
import com.sun.jersey.client.apache4.config.DefaultApacheHttpClient4Config;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.impl.conn.SchemeRegistryFactory;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.util.TextUtils;

public class CustomEurekaJerseyClientBuilder {
private boolean systemSSL;
private String clientName;
private int maxConnectionsPerHost;
private int maxTotalConnections;
private String trustStoreFileName;
private String trustStorePassword;
private String userAgent;
private String proxyUserName;
private String proxyPassword;
private String proxyHost;
private String proxyPort;
private int connectionTimeout;
private int readTimeout;
private int connectionIdleTimeout;
private EncoderWrapper encoderWrapper;
private DecoderWrapper decoderWrapper;

public CustomEurekaJerseyClientBuilder withClientName(String clientName) {
    this.clientName = clientName;
    return this;
}

public CustomEurekaJerseyClientBuilder withUserAgent(String userAgent) {
    this.userAgent = userAgent;
    return this;
}

public CustomEurekaJerseyClientBuilder withConnectionTimeout(int connectionTimeout) {
    this.connectionTimeout = connectionTimeout;
    return this;
}

public CustomEurekaJerseyClientBuilder withReadTimeout(int readTimeout) {
    this.readTimeout = readTimeout;
    return this;
}

public CustomEurekaJerseyClientBuilder withConnectionIdleTimeout(int connectionIdleTimeout) {
    this.connectionIdleTimeout = connectionIdleTimeout;
    return this;
}

public CustomEurekaJerseyClientBuilder withMaxConnectionsPerHost(int maxConnectionsPerHost) {
    this.maxConnectionsPerHost = maxConnectionsPerHost;
    return this;
}

public CustomEurekaJerseyClientBuilder withMaxTotalConnections(int maxTotalConnections) {
    this.maxTotalConnections = maxTotalConnections;
    return this;
}

public CustomEurekaJerseyClientBuilder withProxy(String proxyHost, String proxyPort, String user, String password) {
    this.proxyHost = proxyHost;
    this.proxyPort = proxyPort;
    this.proxyUserName = user;
    this.proxyPassword = password;
    return this;
}

public CustomEurekaJerseyClientBuilder withSystemSSLConfiguration() {
    this.systemSSL = true;
    return this;
}

public CustomEurekaJerseyClientBuilder withTrustStoreFile(String trustStoreFileName, String trustStorePassword) {
    this.trustStoreFileName = trustStoreFileName;
    this.trustStorePassword = trustStorePassword;
    return this;
}

public CustomEurekaJerseyClientBuilder withEncoder(String encoderName) {
    return this.withEncoderWrapper(CodecWrappers.getEncoder(encoderName));
}

public CustomEurekaJerseyClientBuilder withEncoderWrapper(EncoderWrapper encoderWrapper) {
    this.encoderWrapper = encoderWrapper;
    return this;
}

public CustomEurekaJerseyClientBuilder withDecoder(String decoderName, String clientDataAccept) {
    return this.withDecoderWrapper(CodecWrappers.resolveDecoder(decoderName, clientDataAccept));
}

public CustomEurekaJerseyClientBuilder withDecoderWrapper(DecoderWrapper decoderWrapper) {
    this.decoderWrapper = decoderWrapper;
    return this;
}

public EurekaJerseyClient build() {
    MyDefaultApacheHttpClient4Config config = new MyDefaultApacheHttpClient4Config();
    try {
        return new EurekaJerseyClientImpl(connectionTimeout, readTimeout, connectionIdleTimeout, config);
    } catch (Throwable e) {
        throw new RuntimeException("Cannot create Jersey client ", e);
    }
}

class MyDefaultApacheHttpClient4Config extends DefaultApacheHttpClient4Config {

    private static final String PROTOCOL = "https";
    private static final String PROTOCOL_SCHEME = "SSL";
    private static final int HTTPS_PORT = 443;
    private static final String KEYSTORE_TYPE = "JKS";

    MyDefaultApacheHttpClient4Config() {
        MonitoredConnectionManager cm;

        if (systemSSL) {
            cm = createSystemSslCM();
        } else {
            cm = createDefaultSslCM();
        }

        if (proxyHost != null) {
            addProxyConfiguration(cm);
        }

        DiscoveryJerseyProvider discoveryJerseyProvider = new DiscoveryJerseyProvider(encoderWrapper, decoderWrapper);
        getSingletons().add(discoveryJerseyProvider);

        // Common properties to all clients
        cm.setDefaultMaxPerRoute(maxConnectionsPerHost);
        cm.setMaxTotal(maxTotalConnections);
        getProperties().put(ApacheHttpClient4Config.PROPERTY_CONNECTION_MANAGER, cm);

        String fullUserAgentName = (userAgent == null ? clientName : userAgent) + "/v" + DiscoveryBuildInfo.buildVersion();
        getProperties().put(CoreProtocolPNames.USER_AGENT, fullUserAgentName);

        // To pin a client to specific server in case redirect happens, we handle redirects directly
        // (see DiscoveryClient.makeRemoteCall methods).
        getProperties().put(ClientConfig.PROPERTY_FOLLOW_REDIRECTS, Boolean.FALSE);
        getProperties().put(ClientPNames.HANDLE_REDIRECTS, Boolean.FALSE);
    }

    private void addProxyConfiguration(MonitoredConnectionManager cm) {
        if (proxyUserName != null && proxyPassword != null) {
            getProperties().put(ApacheHttpClient4Config.PROPERTY_PROXY_USERNAME, proxyUserName);
            getProperties().put(ApacheHttpClient4Config.PROPERTY_PROXY_PASSWORD, proxyPassword);
        } else {
            // Due to bug in apache client, user name/password must always be set.
            // Otherwise proxy configuration is ignored.
            getProperties().put(ApacheHttpClient4Config.PROPERTY_PROXY_USERNAME, "guest");
            getProperties().put(ApacheHttpClient4Config.PROPERTY_PROXY_PASSWORD, "guest");
        }
        getProperties().put(DefaultApacheHttpClient4Config.PROPERTY_PROXY_URI, "http://" + proxyHost + ":" + proxyPort);
    }

    private MonitoredConnectionManager createSystemSslCM() {
        MonitoredConnectionManager cm;
        X509HostnameVerifier hostnameVerifier = SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;
        SSLConnectionSocketFactory systemSocketFactory = new SSLConnectionSocketFactory(
                (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault(),
                split(System.getProperty("https.protocols")),
                split(System.getProperty("https.cipherSuites")),
                hostnameVerifier);
        SSLSocketFactory sslSocketFactory = new SSLSocketFactoryAdapter(systemSocketFactory);
        SchemeRegistry sslSchemeRegistry = new SchemeRegistry();
        sslSchemeRegistry.register(new Scheme(PROTOCOL, HTTPS_PORT, sslSocketFactory));
        cm = new MonitoredConnectionManager(clientName, sslSchemeRegistry);
        return cm;
    }

    /**
     * @see SchemeRegistryFactory#createDefault()
     */
    private MonitoredConnectionManager createDefaultSslCM() {
        final SchemeRegistry registry = new SchemeRegistry();
        registry.register(
                new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
        registry.register(
                new Scheme("https", 443, new SSLSocketFactoryAdapter(SSLConnectionSocketFactory.getSocket

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...