Java应用调用Microsoft Graph API时Azure登录出现间歇性证书验证失败问题
Java应用调用Microsoft Graph API时Azure登录出现间歇性证书验证失败问题
我最近碰到个特别棘手的问题:我开发的Java应用需要通过Microsoft Graph API访问邮箱,但这个操作时好时坏,失败的时候会抛出无效证书相关的错误,具体的异常日志如下:
2025-02-12 15:50:03.967 [mailbox-listener,,,] WARN HP5CD4411BBZ [reactor-http-nio-2] reactor.util.Loggers$Slf4JLogger - [ca541c56, L:/192.168.255.140:52273 ! R:login.microsoftonline.com/20.190.181.5:443] The connection observed an error io.netty.handler.codec.DecoderException: javax.net.ssl.SSLHandshakeException: General OpenSslEngine problem at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:500) at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1357) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:868) at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724) at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650) at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) at java.base/java.lang.Thread.run(Thread.java:1583) Caused by: javax.net.ssl.SSLHandshakeException: General OpenSslEngine problem at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.handshakeException(ReferenceCountedOpenSslEngine.java:1928) at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.wrap(ReferenceCountedOpenSslEngine.java:853) at java.base/javax.net.ssl.SSLEngine.wrap(SSLEngine.java:564) at io.netty.handler.ssl.SslHandler.wrap(SslHandler.java:1137) at io.netty.handler.ssl.SslHandler.wrapNonAppData(SslHandler.java:981) at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1539) at io.netty.handler.ssl.SslHandler.decodeNonJdkCompatible(SslHandler.java:1377) at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1417) at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530) at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469) ... 17 common frames omitted 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:388) at java.base/sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:271) at java.base/sun.security.validator.Validator.validate(Validator.java:256) at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:284) at java.base/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:144) at io.netty.handler.ssl.EnhancingX509ExtendedTrustManager.checkServerTrusted(EnhancingX509ExtendedTrustManager.java:69) at io.netty.handler.ssl.ReferenceCountedOpenSslClientContext$ExtendedTrustManagerVerifyCallback.verify(ReferenceCountedOpenSslClientContext.java:239) at io.netty.handler.ssl.ReferenceCountedOpenSslContext$AbstractCertificateVerifier.verify(ReferenceCountedOpenSslContext.java:836) at io.netty.internal.tcnative.CertificateVerifierTask.runTask(CertificateVerifierTask.java:36) at io.netty.internal.tcnative.SSLTask.run(SSLTask.java:48) at io.netty.internal.tcnative.SSLTask.run(SSLTask.java:42) at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.runAndResetNeedTask(ReferenceCountedOpenSslEngine.java:1534) at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.access$700(ReferenceCountedOpenSslEngine.java:94) at io.netty.handler.ssl.ReferenceCountedOpenSslEngine$TaskDecorator.run(ReferenceCountedOpenSslEngine.java:1506) at io.netty.handler.ssl.SslHandler.runDelegatedTasks(SslHandler.java:1679) at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1525) ... 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:148) at java.base/sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:129) at java.base/java.security.cert.CertPathBuilder.build(CertPathBuilder.java:297) at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:383) ... 36 common frames omitted
我的项目使用的Maven依赖配置如下:
<properties> <microsoft-graph.version>6.27.0</microsoft-graph.version> <azure-identity.version>1.15.0</azure-identity.version> </properties> <!-- Microsoft Graph API gives access to, among other things, Exchange Cloud mailboxes --> <dependencies> <dependency> <groupId>com.microsoft.graph</groupId> <artifactId>microsoft-graph</artifactId> <version>${microsoft-graph.version}</version> </dependency> <dependency> <!-- This dependency is only needed if you are using a TokenCredential object for authentication --> <groupId>com.azure</groupId> <artifactId>azure-identity</artifactId> <version>${azure-identity.version}</version> </dependency> </dependencies>
对应的Spring Boot配置类代码(原代码存在截断,已补全关键部分以保证可读性):
@Configuration @ConditionalOnProperty(prefix = "mailbox-processor", name = "microsoft-graph.authentication.client-id") @Slf4j public class MicrosoftGraphConfiguration { private static final String[] SCOPES = {"https://graph.microsoft.com/.default"}; private final MicrosoftGraphProperties properties; public MicrosoftGraphConfiguration(final ApplicationProperties applicationProperties) { this.properties = applicationProperties.microsoftGraph(); } @PostConstruct void postConstruct() { log.info("Microsoft graph configuration initialised"); } private InetSocketAddress getProxyAddress() { final var proxy = properties.proxy(); return new InetSocketAddress(proxy.host(), proxy.port()); } @Bean TokenCredential azureClientCredential() { final var baseClient = HttpClient .create() .httpMessageLogFactory(new AbstractHttpMessageLogFactory() { @Override public String common(final HttpMessageArgProvider provider) { return HttpMessageLogUtils.logHttpMessage( provider, Function.identity(), decoderResult -> { if (!decoderResult.isFinished()) { return "unfinished"; } else if (decoderResult.isSuccess()) { return "success"; } else { return "failure(" + decoderResult.cause() + ")"; } }, entry -> { if (HttpHeaders.AUTHORIZATION.equalsIgnoreCase(entry.getKey())) { return "<filtered>"; } else { return entry.getValue(); } } ); } }) .wiretap("login.microsoftonline.com", LogLevel.DEBUG, AdvancedByteBufFormat.TEXTUAL); final var httpClientBuilder = new NettyAsyncHttpClientBuilder(baseClient); final var proxyProperties = properties.proxy(); if (proxyProperties != null) { final var proxyAddress = getProxyAddress(); log.info("Using HTTP proxy {} (for authentication)", proxyAddress); final var proxyOptions = new ProxyOptions(ProxyOptions.Type.HTTP, getProxyAddress()); final var user = proxyProperties.user(); final var password = proxyProperties.password(); if (StringUtils.isNoneBlank(user, password)) { proxyOptions.setCredentials(user, password); } httpClientBuilder.proxy(proxyOptions); } final var httpClient = httpClientBuilder.build(); return new ClientSecretCredentialBuilder() .clientId(properties.authentication().clientId()) .clientSecret(properties.authentication().clientSecret()) .tenantId(properties.authentication().tenantId()) .httpClient(httpClient) .build(); } }
备注:内容来源于stack exchange,提问作者SeverityOne
相关产品推荐
相关产品推荐

