You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Eureka Server的Jersey3ReplicationClient配置mTLS实现节点复制

Spring Cloud Netflix Eureka Server mTLS配置问题:对等节点复制失败

问题背景

已为Eureka Server启用mTLS(设置server.ssl.clientAuth=need)并指定自定义CA作为信任库:

  • Eureka客户端应用可正常通过mTLS注册到Eureka Server
  • Eureka Server自身作为客户端,通过Jersey3DiscoveryClientOptionalArgs配置可完成mTLS注册
  • 核心问题:使用Jersey3ReplicationClient向对等节点复制时触发bad_certificate错误,日志明确显示Empty client certificate chain,即复制客户端未提供身份验证所需的证书/密钥。

关键错误日志

javax.net.ssl|ERROR|04|https-jsse-nio-9080-exec-4|2024-02-03 06:32:42.018 IST|TransportContext.java:370|Fatal (BAD_CERTIFICATE): Empty client certificate chain (
"throwable" : {
  javax.net.ssl.SSLHandshakeException: Empty client certificate chain
    ...
    at com.netflix.eureka.transport.Jersey3ReplicationClient.submitBatchUpdates(Jersey3ReplicationClient.java:116)
    ...
}

当前配置

pom.xml

<properties>
    <java.version>17</java.version>
    <spring-cloud.version>2023.0.0</spring-cloud.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

application.yml

spring:
  application:
    name: EUREKA-SERVER

server:
  port: 9080
  ssl:
    enabled: true
    protocol: TLS
    keyStore: classpath:keystore/eureka_server_mTLS.p12
    keyStorePassword: mypassword
    keyStoreType: PKCS12
    keyAlias: eurekaserver
    clientAuth: need
    trustStore: classpath:truststore/CA_mTLS_Soumyadip.jks
    trustStorePassword: mypassword
    trustStoreType: JKS
  http:
    enabled: false

eureka:
  instance:
    nonSecurePortEnabled: false
    securePortEnabled: true
    securePort: ${server.port}
  client:
    registerWithEureka: true
    fetchRegistry: false
    tls:
      trustStore: classpath:truststore/CA_mTLS_Soumyadip.jks
      trustStorePassword: mypassword
      trustStoreType: JKS
      keyStore: classpath:keystore/eureka_server_mTLS.p12
      keyPassword: mypassword
      keyStorePassword: mypassword
      keyAlias: eurekaserver
      enabled: true
      keyStoreType: PKCS12
    serviceUrl:
      defaultZone: https://192.168.29.6:9081/eureka/

解决方案

Eureka的Jersey3ReplicationClient默认不会复用Eureka Client的TLS配置,需手动为其配置SSL上下文,以下是两种可行方案:

方案1:自定义ReplicationClient并注入SSLContext

创建配置类,手动加载密钥库和信任库构建SSLContext,替换默认的Jersey3ReplicationClient:

import com.netflix.eureka.transport.Jersey3ReplicationClient;
import org.glassfish.jersey.apache.connector.ApacheConnectorProvider;
import org.glassfish.jersey.client.ClientConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.InputStream;
import java.security.KeyStore;

@Configuration
public class EurekaReplicationTlsConfig {

    @Value("${eureka.client.tls.keyStore}")
    private String keyStorePath;

    @Value("${eureka.client.tls.keyStorePassword}")
    private String keyStorePassword;

    @Value("${eureka.client.tls.keyStoreType}")
    private String keyStoreType;

    @Value("${eureka.client.tls.trustStore}")
    private String trustStorePath;

    @Value("${eureka.client.tls.trustStorePassword}")
    private String trustStorePassword;

    @Value("${eureka.client.tls.trustStoreType}")
    private String trustStoreType;

    @Bean
    public Jersey3ReplicationClient jersey3ReplicationClient() throws Exception {
        // 加载密钥库
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        try (InputStream keyStoreStream = getClass().getResourceAsStream(keyStorePath)) {
            keyStore.load(keyStoreStream, keyStorePassword.toCharArray());
        }

        // 加载信任库
        KeyStore trustStore = KeyStore.getInstance(trustStoreType);
        try (InputStream trustStoreStream = getClass().getResourceAsStream(trustStorePath)) {
            trustStore.load(trustStoreStream, trustStorePassword.toCharArray());
        }

        // 初始化密钥/信任管理器工厂
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(trustStore);

        // 构建SSLContext
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);

        // 配置Jersey客户端
        ClientConfig clientConfig = new ClientConfig();
        clientConfig.connectorProvider(new ApacheConnectorProvider());
        clientConfig.property(org.glassfish.jersey.client.ClientProperties.SSL_CONTEXT, sslContext);

        return new Jersey3ReplicationClient(clientConfig);
    }
}

方案2:配置Apache HttpClient连接管理器

通过自定义Apache HttpClient连接管理器,指定SSL套接字工厂,确保复制客户端使用正确的TLS配置:

import com.netflix.eureka.transport.Jersey3ReplicationClient;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.conn.RegistryBuilder;
import org.glassfish.jersey.apache.connector.ApacheClientProperties;
import org.glassfish.jersey.apache.connector.ApacheConnectorProvider;
import org.glassfish.jersey.client.ClientConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.net.ssl.SSLContext;

@Configuration
public class EurekaReplicationHttpClientConfig {

    // 复用方案1中的SSLContext构建逻辑,此处省略...

    @Bean
    public Jersey3ReplicationClient jersey3ReplicationClient(SSLContext sslContext) {
        SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext);
        org.apache.http.config.Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("https", sslSocketFactory)
                .build();

        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        ClientConfig clientConfig = new ClientConfig();
        clientConfig.connectorProvider(new ApacheConnectorProvider());
        clientConfig.property(ApacheClientProperties.CONNECTION_MANAGER, connectionManager);

        return new Jersey3ReplicationClient(clientConfig);
    }
}

注意事项

  • 确保密钥库/信任库路径正确,classpath:前缀对应src/main/resources目录下的文件
  • 验证证书有效期、密钥别名与配置一致,避免因证书无效导致握手失败
  • 无需修改JVM全局cacerts信任库,通过代码加载自定义信任库可实现环境隔离

内容的提问来源于stack exchange,提问作者Soumyadip Sharma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 03:59:54