2017-02-17 24 views
2

Я пытаюсь включить https для сервера eureka весеннего облака. YAML конфигурации:Как переопределить контекст ssl по умолчанию для обнаружения по умолчанию Spring Cloud Eureka?

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 

Затем я начинаю клиент для регистрации на сервере. Я не импортировал самозаверяющий сертификат, таким образом, в cource. Я получаю excpetion. sun.security.provider.certpath.SunCertPathBuilderException: не удалось найти допустимый путь сертификации для запрошенной цели. Я не хочу импортировать сертификат, потому что есть много экземпляров, и управление сертификатами очень дорого. Таким образом, я помещаю cert в classpath и загружаю его во время запуска. Я переопределяю контекст клиента SSL по умолчанию и SSL сокет facotory путем добавления кодов

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

Он отлично работает при вызове другого клиента эврика с помощью симулировать. Однако при регистрации на сервере eureka он не прилагает никаких усилий. Я проверяю исходный код и обнаруживаю, что клиент открытия eureka с использованием трикотажа, джерси вызывает клиент http apache. Беда в том, что использовать SchemeRegistryFactory.createDefault() который будет окончательный вызов SSLContexts.createDefault(), которые не будут приняты свойства системы во внимание. Другими словами, этот http-клиент не будет использовать мои собственные SSLContexts. Итак, мой вопрос: существует ли способ добавить/resigter/заменить клиент http по умолчанию в eureka client?

ответ

5

Наконец-то я нашел решение после многократного копания исходного кода. Я использую версию Camden.SR5, которая будет называть eureka-client-1.4.12.

Если вы предоставляете EurekaJerseyClient в DiscoveryClientOptionalArgs, то клиент Discovery не intial по умолчанию. Часть кода из класса 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); 
... 
} 

Тогда я добавить класс, чтобы сделать DiscoveryClientOptionalArgs боб.

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

}

Застраховать свой заказ EurekaJerseyClient работает хорошо, я раздвоенный код из EurekaJerseyClientImpl и сделал некоторые изменения.

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.getSocketFactory()))); 
     return new MonitoredConnectionManager(clientName, registry); 
    } 

    private String[] split(final String s) { 
     if (TextUtils.isBlank(s)) { 
      return null; 
     } 
     return s.split(" *, *"); 
    } 
} 
} 

Надеюсь, что это может помочь тем, кто нелегко импортировать сертификат в Production JVM, как и я.