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

Spring Boot自动配置Azure EventHub SPN认证SSL握手问题求助

问题

基于Java Spring Boot自动配置的应用,通过SPN认证向Azure EventHub发送消息时遇到SSL握手证书问题,使用连接字符串连接正常,仅SPN认证时出错。

环境信息

Java 17
Spring Boot: solace-spring-boot-starter:2.0.0
Spring Azure: spring-cloud-azure-starter-integration-eventhubs:5.13.0

EventHubProducerService代码

package com.mysample.producer;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.azure.messaging.eventhubs.EventData;
import com.azure.messaging.eventhubs.EventHubProducerClient;

import java.util.Collections;

@Service
public class EventHubProducerService {

    @Autowired
    private EventHubProducerClient producerClient;

    public void sendEventHubMessage(String message){
        producerClient.send(Collections.singletonList(new EventData(message)));
        log.info("message sent successfully to EventHub");
    }
}

application.yml配置

spring.cloud.azure:
  eventhubs:
    namespace: "<namespaceName>"
    eventHubName: "<eventHubName>"
  credential:
    client-id: "<SPN ClientId>"
    client-secret: "<SPN SecretValue>"
  profile:
    tenant-id: "<TenantId>"

错误日志

2024-07-11T14:57:45.612-04:00  WARN 9632 --- [ctor-http-nio-1] r.netty.http.client.HttpClientConnect    : [330ebfae, L:/<ip>:<port> - R:login.microsoftonline.com/<ip>:443] The connection observed an error

javax.net.ssl.SSLHandshakeException: General OpenSslEngine problem
        at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.handshakeException(ReferenceCountedOpenSslEngine.java:1927) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.wrap(ReferenceCountedOpenSslEngine.java:848) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at java.base/javax.net.ssl.SSLEngine.wrap(SSLEngine.java:564) ~[na:na]
        at io.netty.handler.ssl.SslHandler.wrap(SslHandler.java:1129) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.SslHandler.wrapNonAppData(SslHandler.java:973) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1509) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.SslHandler.decodeNonJdkCompatible(SslHandler.java:1347) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1387) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530) ~[netty-codec-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469) ~[netty-codec-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290) ~[netty-codec-4.1.111.Final.jar:4.1.111.Final]        
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1407) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]  
        at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]     
        at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) ~[netty-transport-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:994) ~[netty-common-4.1.111.Final.jar:4.1.111.Final] 
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.111.Final.jar:4.1.111.Final]        
        at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:439) ~[na:na]
        at java.base/sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:306) ~[na:na]
        at java.base/sun.security.validator.Validator.validate(Validator.java:264) ~[na:na]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:285) ~[na:na]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:144) ~[na:na]
        at io.netty.handler.ssl.EnhancingX509ExtendedTrustManager.checkServerTrusted(EnhancingX509ExtendedTrustManager.java:69) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.ReferenceCountedOpenSslClientContext$ExtendedTrustManagerVerifyCallback.verify(ReferenceCountedOpenSslClientContext.java:235) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.ReferenceCountedOpenSslContext$AbstractCertificateVerifier.verify(ReferenceCountedOpenSslContext.java:797) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.internal.tcnative.CertificateVerifierTask.runTask(CertificateVerifierTask.java:36) ~[netty-tcnative-classes-2.0.65.Final.jar:2.0.65.Final]
        at io.netty.internal.tcnative.SSLTask.run(SSLTask.java:48) ~[netty-tcnative-classes-2.0.65.Final.jar:2.0.65.Final]
        at io.netty.internal.tcnative.SSLTask.run(SSLTask.java:42) ~[netty-tcnative-classes-2.0.65.Final.jar:2.0.65.Final]
        at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.runAndResetNeedTask(ReferenceCountedOpenSslEngine.java:1533) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.access$700(ReferenceCountedOpenSslEngine.java:94) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.ReferenceCountedOpenSslEngine$TaskDecorator.run(ReferenceCountedOpenSslEngine.java:1505) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.SslHandler.runDelegatedTasks(SslHandler.java:1649) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1495) ~[netty-handler-4.1.111.Final.jar:4.1.111.Final]
        ... 21 common frames omitted
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at java.base/sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141) ~[na:na]
        at java.base/sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126) ~[na:na]
        at java.base/java.security.cert.CertPathBuilder.build(CertPathBuilder.java:297) ~[na:na]
        at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:434) ~[na:na]
        ... 36 common frames omitted

解决思路

核心原因

错误指向login.microsoftonline.com的SSL证书未被JVM信任。SPN认证需要先向Azure AD的OAuth2端点请求令牌,这个请求的SSL证书不在JVM默认信任存储中;而连接字符串方式直接与EventHub建立连接,无需走Azure AD认证,因此没有这个问题。

具体解决步骤

  1. 获取Azure AD根证书

    • 访问https://login.microsoftonline.com,在浏览器中导出网站的根CA证书(通常为DigiCert或Microsoft颁发的根证书),保存为.cer格式。
  2. 导入证书到JVM全局信任存储

    • 找到JDK安装目录下的信任存储文件:Java 17路径为$JAVA_HOME/lib/security/cacerts。
    • 使用keytool命令导入证书:
      keytool -import -alias azure-ad-root -file /path/to/your/certificate.cer -keystore $JAVA_HOME/lib/security/cacerts
      
    • 默认密码为changeit,按提示完成导入操作。
  3. 验证证书导入结果

    • 运行以下命令确认证书已成功导入:
      keytool -list -keystore $JAVA_HOME/lib/security/cacerts -alias azure-ad-root
      
  4. 替代方案:自定义SSL上下文(无需修改全局信任存储)

    • 如果不想修改JVM全局配置,可通过Spring配置自定义SSL上下文,仅让EventHub客户端信任目标证书:
      @Configuration
      public class AzureSslConfig {
          @Bean
          public EventHubProducerClientBuilderCustomizer eventHubProducerClientBuilderCustomizer() throws Exception {
              // 加载本地证书文件
              CertificateFactory cf = CertificateFactory.getInstance("X.509");
              File certFile = new File("path/to/azure-ad-cert.cer");
              X509Certificate cert = (X509Certificate) cf.generateCertificate(new FileInputStream(certFile));
      
              // 构建信任管理器
              TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
              KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
              ks.load(null);
              ks.setCertificateEntry("azure-ad-root", cert);
              tmf.init(ks);
      
              // 创建自定义SSL上下文
              SslContext sslContext = SslContextBuilder.forClient()
                      .trustManager(tmf)
                      .build();
      
              // 注入到EventHub客户端构建器
              return builder -> builder.httpClient(new NettyAsyncHttpClientBuilder()
                      .sslContext(sslContext)
                      .build());
          }
      }
      
  5. 排查网络环境

    • 若在企业内网环境,需确认是否有代理拦截SSL连接,此时需将企业代理的根证书也导入到信任存储中。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 05:14:57