복붙노트

[SPRING] Spring Cloud 유레카 기본 검색 클라이언트 기본 SSL 컨텍스트를 재정의하는 방법?

SPRING

Spring Cloud 유레카 기본 검색 클라이언트 기본 SSL 컨텍스트를 재정의하는 방법?

스프링 클라우드 유레카 서버에 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 : 요청한 대상에 대한 유효한 인증서 경로를 찾을 수 없습니다. 충분한 인스턴스와 인증서 관리 비용이 많이 들기 때문에 인증서를 가져오고 싶지 않습니다. 따라서 나는 classpath에 인증서를 넣고 시작하는 동안 그것을로드합니다. 코드를 추가하여 클라이언트 기본 SSL 컨텍스트와 SSL 소켓 외양을 재정의합니다.

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

그것은 feign을 사용하여 다른 유레카 클라이언트를 호출하면 잘 작동합니다. 그러나 유레카 서버에 등록 할 때 아무런 노력이 없습니다. 나는 소스 코드를 확인하고 jersey를 사용하여 유레카 발견 클라이언트를 찾고 저지는 http 아파치 클라이언트를 호출한다. 문제는 SchemeRegistryFactory.createDefault ()를 사용하여 마지막으로 SSLContexts.createDefault ()를 호출하여 시스템 속성을 고려하지 않을 것입니다. 즉,이 http 클라이언트는 내 사용자 지정 SSLContext를 토큰하지 않습니다. 그래서 내 질문에, 거기에 / resigter / 유레카 발견 클라이언트에서 기본 http 클라이언트를 대체하는 방법은?

해결법

  1. ==============================

    1.마지막으로 소스 코드를 여러 번 파고 들자 해결책을 찾았습니다. 나는 eureka-client-1.4.12를 호출 할 Camden.SR5 버전을 사용하고 있습니다.

    마지막으로 소스 코드를 여러 번 파고 들자 해결책을 찾았습니다. 나는 eureka-client-1.4.12를 호출 할 Camden.SR5 버전을 사용하고 있습니다.

    DiscoveryClientOptionalArgs에 EurekaJerseyClient를 제공하는 경우 검색 클라이언트는 기본 클라이언트가 아닙니다. 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 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;
    }
    

    }

    내 사용자 지정 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(" *, *");
        }
    }
    }
    

    이것이 나처럼 프로덕션 JVM에서 인증서를 가져 오기가 쉽지 않은 사람들을 도울 수 있기를 바랍니다.

  2. ==============================

    2.나는 Finchley.M9 봄 구름에있는 유레카 클라이언트에게 SSL 컨텍스트를 주입 할 수 있었다.

    나는 Finchley.M9 봄 구름에있는 유레카 클라이언트에게 SSL 컨텍스트를 주입 할 수 있었다.

    @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();
        }
    }
    
  3. from https://stackoverflow.com/questions/42289196/how-to-override-spring-cloud-eureka-default-discovery-client-default-ssl-context by cc-by-sa and MIT license