如何为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
相关产品推荐
相关产品推荐

