16-HTTPS在Eureka中的配置

282 阅读3分钟

euraka集群JerseyReplicationClient找不到证书问题 源码分析如下: eureka服务器需要在启动类上加上@EnableEurekaServer注解,改类会自动的加载一个配置类 EurekaServerAutoConfiguration,其创建集群节点代码分析如下: image.png RefreshablePeerEurekaNodes 类如下: image.png 在createPeerEurekaNode方法里面,为每个节点创建备份客户端JerseyReplicationClient,具体的创建方法如下

public static JerseyReplicationClient createReplicationClient(EurekaServerConfig config, ServerCodecs serverCodecs, String serviceUrl) {
        String name = JerseyReplicationClient.class.getSimpleName() + ": " + serviceUrl + "apps/: ";

        EurekaJerseyClient jerseyClient;
        try {
            String hostname;
            try {
                hostname = new URL(serviceUrl).getHost();
            } catch (MalformedURLException e) {
                hostname = serviceUrl;
            }

            String jerseyClientName = "Discovery-PeerNodeClient-" + hostname;
            EurekaJerseyClientBuilder clientBuilder = new EurekaJerseyClientBuilder()
                    .withClientName(jerseyClientName)
                    .withUserAgent("Java-EurekaClient-Replication")
                    .withEncoderWrapper(serverCodecs.getFullJsonCodec())
                    .withDecoderWrapper(serverCodecs.getFullJsonCodec())
                    .withConnectionTimeout(config.getPeerNodeConnectTimeoutMs())
                    .withReadTimeout(config.getPeerNodeReadTimeoutMs())
                    .withMaxConnectionsPerHost(config.getPeerNodeTotalConnectionsPerHost())
                    .withMaxTotalConnections(config.getPeerNodeTotalConnections())
                    .withConnectionIdleTimeout(config.getPeerNodeConnectionIdleTimeoutSeconds());

发现工具类EurekaJerseyClientBuilder自定义备份客户端的实现 image.png

具体的代码实现:

/**
 * 功能描述
 *
 * @since 2022-11-08
 */
@Configuration
@EnableConfigurationProperties(ClientSslProperty.class)
public class EurekaServerConfigTest {
    private static final Logger LOGGER = LoggerFactory.getLogger(EurekaServerConfigMy.class);

    @Autowired
    private ApplicationInfoManager applicationInfoManager;

    @Autowired
    private EurekaServerConfig eurekaServerConfig;

    @Autowired
    private EurekaClientConfig eurekaClientConfig;

    @Autowired
    private ClientSslProperty clientSslProperty;

    @Autowired
    private AesCiperServiceUtil aesCiperService;

    /**
     * 功能描述:向容器中注入自己的PeerEurekaNodes
     *
     * @param registry 与Eureka中registry相关的接口
     * @param serverCodecs Eureka的serverCodecs接口
     * @return 返回PeerEurekaNodes
     */
    @Bean
    public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry, ServerCodecs serverCodecs) {
        RefreshablePeerEurekaNodes peerEurekaNodesRefreshable = new RefreshablePeerEurekaNodes(registry,
            this.eurekaServerConfig, this.eurekaClientConfig, serverCodecs, this.applicationInfoManager);
        peerEurekaNodesRefreshable.setClientSslProperty(clientSslProperty);
        peerEurekaNodesRefreshable.setAesCiperService(aesCiperService);
        return peerEurekaNodesRefreshable;
    }

    /**
     * Refresh Peer Eureka Nodes
     *
     * @since 2022-04-25
     */
    static class RefreshablePeerEurekaNodes extends PeerEurekaNodes
        implements ApplicationListener<EnvironmentChangeEvent> {
        private ClientSslProperty clientSslProperty;

        private AesCiperServiceUtil aesCiperService;

        RefreshablePeerEurekaNodes(final PeerAwareInstanceRegistry registry, final EurekaServerConfig serverConfig,
            final EurekaClientConfig clientConfig, final ServerCodecs serverCodecs,
            final ApplicationInfoManager applicationInfoManager) {
            super(registry, serverConfig, clientConfig, serverCodecs, applicationInfoManager);
        }

        public void setClientSslProperty(final ClientSslProperty clientSslProperty) {
            this.clientSslProperty = clientSslProperty;
        }

        public void setAesCiperService(AesCiperServiceUtil aesCiperService) {
            this.aesCiperService = aesCiperService;
        }

        @Override
        public void onApplicationEvent(final EnvironmentChangeEvent event) {
            if (shouldUpdateNodes(event.getKeys())) {
                updatePeerEurekaNodes(resolvePeerUrls());
            }
        }

        /**
         * 功能描述:Check whether specific properties have changed.
         *
         * @param keys event key的集合
         * @return 是否需要更新节点
         */
        private boolean shouldUpdateNodes(final Set<String> keys) {
            if (keys == null) {
                CodeCCUtils.warn(LOGGER, "keys is null.");
                return false;
            }
            if (clientConfig.shouldUseDnsForFetchingServiceUrls()) {
                return false;
            }
            if (keys.contains("eureka.client.region")) {
                return true;
            }
            return keys.stream()
                .filter(Objects::nonNull)
                .anyMatch(key -> key.startsWith("eureka.client.service-url.")
                    || key.startsWith("eureka.client.availability-zones."));
        }

        @Override
        protected PeerEurekaNode createPeerEurekaNode(String peerEurekaNodeUrl) {
            String targetHost = "host";
            HttpReplicationClient replicationClient = null;
            try {
                CodeCCUtils.info(LOGGER, "create peer eureka node by SSL.");
                replicationClient = createReplicationClient(serverConfig, serverCodecs, peerEurekaNodeUrl,
                    clientSslProperty, aesCiperService);
                targetHost = hostFromUrl(peerEurekaNodeUrl);
                if (targetHost == null) {
                    targetHost = "host";
                }
            } catch (UnrecoverableKeyException | CertificateOperationException e) {
                CodeCCUtils.errorWithParamters(LOGGER, "Create peer eureka node error. {}", e);
            }
            return new PeerEurekaNode(registry, targetHost, peerEurekaNodeUrl, replicationClient, serverConfig);
        }

        /**
         * Create Replication Client
         *
         * @param config Eureka的配置
         * @param serverCodecs Eureka的serverCodecs接口
         * @param serviceUrl 服务的url
         * @param clientSslProperty ssl相关配置
         * @param aesCiperService AES加解密组件
         * @return Jersey Replication Client
         * @throws UnrecoverableKeyException Unrecoverable Key 异常
         * @throws CertificateOperationException 证书操作异常
         */
        private static JerseyReplicationClient createReplicationClient(EurekaServerConfig config,
            ServerCodecs serverCodecs, String serviceUrl, ClientSslProperty clientSslProperty,
            AesCiperServiceUtil aesCiperService) throws UnrecoverableKeyException, CertificateOperationException {
            String hostname;
            try {
                hostname = new URL(serviceUrl).getHost();
            } catch (MalformedURLException e) {
                hostname = serviceUrl;
            }

            // 加上SSL的配置
            String keyStorePwd = aesCiperService.decryptStr(clientSslProperty.getKeyStorePassword());
            String trustStorePwd = aesCiperService.decryptStr(clientSslProperty.getTrustStorePassword());
            SSLContext sslContext;
            try {
                sslContext = SSLUtil.getSSLContext(clientSslProperty.getKeyStore(), keyStorePwd,
                    clientSslProperty.getTrustStore(), trustStorePwd);
            } finally {
                keyStorePwd = null;
                trustStorePwd = null;
            }

            String jerseyClientName = "Discovery-PeerNodeClient-" + hostname;
            EurekaJerseyClientImpl.EurekaJerseyClientBuilder clientBuilder =
                new EurekaJerseyClientImpl.EurekaJerseyClientBuilder().withClientName(jerseyClientName)
                    .withUserAgent("Java-EurekaClient-Replication")
                    .withEncoderWrapper(serverCodecs.getFullJsonCodec())
                    .withDecoderWrapper(serverCodecs.getFullJsonCodec())
                    .withCustomSSL(sslContext)
                    .withConnectionTimeout(config.getPeerNodeConnectTimeoutMs())
                    .withReadTimeout(config.getPeerNodeReadTimeoutMs())
                    .withMaxConnectionsPerHost(config.getPeerNodeTotalConnectionsPerHost())
                    .withMaxTotalConnections(config.getPeerNodeTotalConnections())
                    .withConnectionIdleTimeout(config.getPeerNodeConnectionIdleTimeoutSeconds());
            clientBuilder.withHostnameVerifier(HostnameVerifierUtil.getHostnameVerifier());
            String ip = null;
            try {
                Optional<InetAddress> inetAddress = getLocalHostExactAddress();
                if (inetAddress.isPresent()) {
                    ip = inetAddress.get().getHostAddress();
                } else {
                    CodeCCUtils.warn(LOGGER, "get inetAddress is null.");
                }
            } catch (SocketException e) {
                CodeCCUtils.errorWithParamters(LOGGER, "Cannot find localhost ip. {}", e);
            }
            EurekaJerseyClient jerseyClient = clientBuilder.build();
            ApacheHttpClient4 jerseyApacheClient = jerseyClient.getClient();
            jerseyApacheClient.addFilter(new DynamicGZIPContentEncodingFilter(config));

            EurekaServerIdentity identity = new EurekaServerIdentity(ip);
            jerseyApacheClient.addFilter(new EurekaIdentityHeaderFilter(identity));

            return new JerseyReplicationClient(jerseyClient, serviceUrl);
        }

        private static Optional<InetAddress> getLocalHostExactAddress() throws SocketException {
            Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
            if (networkInterfaces == null) {
                return Optional.empty();
            }
            InetAddress candidateAddress = null;
            while (networkInterfaces.hasMoreElements()) {
                NetworkInterface iface = networkInterfaces.nextElement();
                ResponseVO<InetAddress> responseVo = getInetAddress(iface, candidateAddress);
                if (responseVo.getResultCode() == ResultCodeEnum.SUCCEED.getValue()) {
                    return Optional.ofNullable(responseVo.getData());
                }
                candidateAddress = responseVo.getData();
            }
            // 如果出去loopback回环地之外无其它地址了,那就回退到原始方案吧
            return Optional.ofNullable(candidateAddress);
        }

        private static ResponseVO<InetAddress> getInetAddress(NetworkInterface iface, InetAddress candidateAddress) {
            InetAddress temp = candidateAddress;
            ResponseVO<InetAddress> responseVo = new ResponseVO<>(ResultCodeEnum.SUCCEED.getValue());
            for (Enumeration<InetAddress> inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements();) {
                InetAddress inetAddr = inetAddrs.nextElement();
                // 排除loopback回环类型地址(不管是IPv4还是IPv6 只要是回环地址都会返回true)
                if (!inetAddr.isLoopbackAddress()) {
                    if (inetAddr.isSiteLocalAddress()) {
                        // 如果是site-local地址,就是它了 就是我们要找的
                        // 绝大部分情况下都会在此处返回你的ip地址值
                        responseVo.setData(inetAddr);
                        return responseVo;
                    }
                    // 若不是site-local地址 那就记录下该地址当作候选
                    if (temp == null) {
                        temp = inetAddr;
                    }
                }
            }
            responseVo.setResultCode(ResultCodeEnum.FAILED.getValue());
            responseVo.setData(temp);
            return responseVo;
        }

    }
}

更多内容关注微信公众号 ”前后端技术精选“,或者语雀,里面有更多知识:www.yuque.com/riverzmm/uu… 《安全》