Skip to content

Spring AI 2 authentication to Azure OpenAI fails #6549

Description

@fbeaufume

Bug description

Authentication to Azure OpenAI performs an SSL connection to www.example.com, that fails in some cases (e.g. if a custom certificate is required).

Environment

Fails with Spring AI 2.0.0 but worked fine with Spring AI 1.x.

Steps to reproduce

We use Spring AI with LLMs from Azure OpenAI.

The Azure OpenAI authentication does not use API key but the Azure CLI (for local dev) or managed identities (when hosted on Azure).

With Spring AI 1.x it worked fine. But with Spring AI 2.0.0 it now fails because of an SSL handshake failure.

It seems to come from a connection to https://www.example.com performed by the azure-identity library:

// Method from com.azure.identity.AuthenticationUtil of azure-identity 1.18.3 library from Spring Cloud Azure 7.3.0
public static Supplier<String> getBearerTokenSupplier(TokenCredential credential, String... scopes) {
    HttpPipeline pipeline
        = new HttpPipelineBuilder().policies(new BearerTokenAuthenticationPolicy(credential, scopes)).build();
    return () -> {
        // This request will never need to go anywhere; it is simply to cause the policy to interact with
        // the user's credential
        HttpRequest req = new HttpRequest(HttpMethod.GET, "https://www.example.com");
        try (HttpResponse res = pipeline.sendSync(req, Context.NONE)) {
            return res.getRequest().getHeaders().get(HttpHeaderName.AUTHORIZATION).getValue().split(" ")[1];
        }
    };
}

That code is called from org.springframework.ai.openai.setup.AzureInternalOpenAiHelper#getAzureCredential (line 40) called by org.springframework.ai.openai.setup.OpenAiSetup#azureAuthentication (line 328).

This fails in our company because some specific certificates are required to perform HTTPS connections.

The LangChain4j counterpart works fine (no SSL error):

  • Same Spring Boot 4.1.0 version
  • Same azure-identity from Spring Cloud Azure 7.3.0
  • Same authentication mode (Azure CLI during local dev)
  • But with LangChain4j 1.17.0 instead of Spring AI 2.0.0

We did not have to import the certificates for Spring AI 1.x. We also do not have to import the certificates for LangChain4j.

Moreover, connecting to www.example.com seems quite weird.

Expected behavior

Azure OpenAI authentication should work fine, and no SSL connection to www.example.com should be attempted.

If we add the certificates to the JVM, then it works fine. But doing so for multiple applications is a problem.

Considering that (a) it worked fine with Spring AI 1.x and (b) it works fine with LangChain4j, this seems like a Spring AI bug or regression.

What do you think ? Could this be fixed ?

Sample stack trace

java.io.UncheckedIOException: javax.net.ssl.SSLHandshakeException: General OpenSslEngine problem
        at com.azure.core.http.netty.NettyAsyncHttpClient.sendSync(NettyAsyncHttpClient.java:200) ~[azure-core-http-netty-1.16.4.jar:1.16.4]
        at com.azure.core.http.HttpPipelineNextSyncPolicy.processSync(HttpPipelineNextSyncPolicy.java:51) ~[azure-core-1.58.0.jar:1.58.0]
        at com.azure.core.http.policy.BearerTokenAuthenticationPolicy.processSync(BearerTokenAuthenticationPolicy.java:175) ~[azure-core-1.58.0.jar:1.58.0]
        at com.azure.core.http.HttpPipelineNextSyncPolicy.processSync(HttpPipelineNextSyncPolicy.java:53) ~[azure-core-1.58.0.jar:1.58.0]
        at com.azure.core.http.HttpPipeline.sendSync(HttpPipeline.java:138) ~[azure-core-1.58.0.jar:1.58.0]
        at com.azure.identity.AuthenticationUtil.lambda$getBearerTokenSupplier$0(AuthenticationUtil.java:56) ~[azure-identity-1.18.3.jar:1.18.3]
        at com.openai.credential.BearerTokenCredential.token(BearerTokenCredential.kt:35) ~[openai-java-core-4.39.1.jar:4.39.1]
        at com.openai.core.ClientOptions.securityHeaders$openai_java_core(ClientOptions.kt:743) ~[openai-java-core-4.39.1.jar:4.39.1]
        at com.openai.core.PrepareRequest.prepare(PrepareRequest.kt:25) ~[openai-java-core-4.39.1.jar:4.39.1]
        at com.openai.services.blocking.chat.ChatCompletionServiceImpl$WithRawResponseImpl.create(ChatCompletionServiceImpl.kt:134) ~[openai-java-core-4.39.1.jar:4.39.1]
        at com.openai.services.blocking.chat.ChatCompletionServiceImpl.create(ChatCompletionServiceImpl.kt:63) ~[openai-java-core-4.39.1.jar:4.39.1]
        at com.openai.services.blocking.chat.ChatCompletionService.create(ChatCompletionService.kt:64) ~[openai-java-core-4.39.1.jar:4.39.1]
        at org.springframework.ai.openai.OpenAiChatModel.lambda$internalCall$2(OpenAiChatModel.java:217) ~[spring-ai-openai-2.0.0.jar:2.0.0]
        at io.micrometer.observation.Observation.observe(Observation.java:634) ~[micrometer-observation-1.17.0.jar:1.17.0]
        at org.springframework.ai.openai.OpenAiChatModel.internalCall(OpenAiChatModel.java:215) ~[spring-ai-openai-2.0.0.jar:2.0.0]
        at org.springframework.ai.openai.OpenAiChatModel.call(OpenAiChatModel.java:194) ~[spring-ai-openai-2.0.0.jar:2.0.0]
        at org.springframework.ai.chat.client.advisor.ChatModelCallAdvisor.adviseCall(ChatModelCallAdvisor.java:58) ~[spring-ai-client-chat-2.0.0.jar:2.0.0]
        at org.springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.lambda$nextCall$1(DefaultAroundAdvisorChain.java:117) ~[spring-ai-client-chat-2.0.0.jar:2.0.0]
        at io.micrometer.observation.Observation.observe(Observation.java:634) ~[micrometer-observation-1.17.0.jar:1.17.0]
        at org.springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.nextCall(DefaultAroundAdvisorChain.java:116) ~[spring-ai-client-chat-2.0.0.jar:2.0.0]
        at org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor.adviseCall(SimpleLoggerAdvisor.java:76) ~[spring-ai-client-chat-2.0.0.jar:2.0.0]
        at org.springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.lambda$nextCall$1(DefaultAroundAdvisorChain.java:117) ~[spring-ai-client-chat-2.0.0.jar:2.0.0]
        at io.micrometer.observation.Observation.observe(Observation.java:634) ~[micrometer-observation-1.17.0.jar:1.17.0]
        at org.springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.nextCall(DefaultAroundAdvisorChain.java:116) ~[spring-ai-client-chat-2.0.0.jar:2.0.0]
        at org.springframework.ai.chat.client.advisor.ToolCallingAdvisor.adviseCall(ToolCallingAdvisor.java:150) ~[spring-ai-client-chat-2.0.0.jar:2.0.0]
        at org.springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.lambda$nextCall$1(DefaultAroundAdvisorChain.java:117) ~[spring-ai-client-chat-2.0.0.jar:2.0.0]
        at io.micrometer.observation.Observation.observe(Observation.java:634) ~[micrometer-observation-1.17.0.jar:1.17.0]
        at org.springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.nextCall(DefaultAroundAdvisorChain.java:116) ~[spring-ai-client-chat-2.0.0.jar:2.0.0]
        at org.springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.lambda$doGetObservableChatClientResponse$1(DefaultChatClient.java:658) ~[spring-ai-client-chat-2.0.0.jar:2.0.0]
        at io.micrometer.observation.Observation.observe(Observation.java:634) ~[micrometer-observation-1.17.0.jar:1.17.0]
        at org.springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.doGetObservableChatClientResponse(DefaultChatClient.java:656) ~[spring-ai-client-chat-2.0.0.jar:2.0.0]
        at org.springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.doGetObservableChatClientResponse(DefaultChatClient.java:636) ~[spring-ai-client-chat-2.0.0.jar:2.0.0]
        at org.springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.content(DefaultChatClient.java:631) ~[spring-ai-client-chat-2.0.0.jar:2.0.0]
        at com.redacted.service.DocumentService.summarize(DocumentService.java:32) ~[classes/:na]
        at com.redacted.SpringAiDocumentApplication.summarize(SpringAiDocumentApplication.java:39) ~[classes/:na]
        at com.redacted.SpringAiDocumentApplication.run(SpringAiDocumentApplication.java:30) ~[classes/:na]
        at org.springframework.boot.SpringApplication.lambda$callRunner$1(SpringApplication.java:792) ~[spring-boot-4.1.0.jar:4.1.0]
        at org.springframework.util.function.ThrowingConsumer$1.acceptWithException(ThrowingConsumer.java:82) ~[spring-core-7.0.8.jar:7.0.8]
        at org.springframework.util.function.ThrowingConsumer.accept(ThrowingConsumer.java:60) ~[spring-core-7.0.8.jar:7.0.8]
        at org.springframework.util.function.ThrowingConsumer$1.accept(ThrowingConsumer.java:86) ~[spring-core-7.0.8.jar:7.0.8]
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:800) ~[spring-boot-4.1.0.jar:4.1.0]
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:791) ~[spring-boot-4.1.0.jar:4.1.0]
        at org.springframework.boot.SpringApplication.lambda$callRunners$0(SpringApplication.java:776) ~[spring-boot-4.1.0.jar:4.1.0]
        at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) ~[na:na]
        at java.base/java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:357) ~[na:na]
        at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:510) ~[na:na]
        at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
        at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) ~[na:na]
        at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) ~[na:na]
        at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
        at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) ~[na:na]
        at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:776) ~[spring-boot-4.1.0.jar:4.1.0]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:328) ~[spring-boot-4.1.0.jar:4.1.0]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1365) ~[spring-boot-4.1.0.jar:4.1.0]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1354) ~[spring-boot-4.1.0.jar:4.1.0]
        at com.redacted.SpringAiDocumentApplication.main(SpringAiDocumentApplication.java:19) ~[classes/:na]
Caused by: javax.net.ssl.SSLHandshakeException: General OpenSslEngine problem
        at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.handshakeException(ReferenceCountedOpenSslEngine.java:1937) ~[netty-handler-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.wrap(ReferenceCountedOpenSslEngine.java:877) ~[netty-handler-4.2.15.Final.jar:4.2.15.Final]
        at java.base/javax.net.ssl.SSLEngine.wrap(SSLEngine.java:564) ~[na:na]
        at io.netty.handler.ssl.SslHandler.wrap(SslHandler.java:1150) ~[netty-handler-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.handler.ssl.SslHandler.wrapNonAppData(SslHandler.java:1003) ~[netty-handler-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1562) ~[netty-handler-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.handler.ssl.SslHandler.decodeNonJdkCompatible(SslHandler.java:1397) ~[netty-handler-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1437) ~[netty-handler-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:545) ~[netty-codec-base-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:484) ~[netty-codec-base-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296) ~[netty-codec-base-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1429) ~[netty-transport-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918) ~[netty-transport-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:176) ~[netty-transport-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.handle(AbstractNioChannel.java:445) ~[netty-transport-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.channel.nio.NioIoHandler$DefaultNioRegistration.handle(NioIoHandler.java:388) ~[netty-transport-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.channel.nio.NioIoHandler.processSelectedKey(NioIoHandler.java:596) ~[netty-transport-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.channel.nio.NioIoHandler.processSelectedKeysOptimized(NioIoHandler.java:571) ~[netty-transport-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.channel.nio.NioIoHandler.processSelectedKeys(NioIoHandler.java:512) ~[netty-transport-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.channel.nio.NioIoHandler.run(NioIoHandler.java:484) ~[netty-transport-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.channel.SingleThreadIoEventLoop.runIo(SingleThreadIoEventLoop.java:225) ~[netty-transport-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.channel.SingleThreadIoEventLoop.run(SingleThreadIoEventLoop.java:196) ~[netty-transport-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:1195) ~[netty-common-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.2.15.Final.jar:4.2.15.Final]
        at java.base/java.lang.Thread.run(Thread.java:1583) ~[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:388) ~[na:na]
        at java.base/sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:271) ~[na:na]
        at java.base/sun.security.validator.Validator.validate(Validator.java:256) ~[na:na]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:284) ~[na:na]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:144) ~[na:na]
        at io.netty.handler.ssl.EnhancingX509ExtendedTrustManager.checkServerTrusted(EnhancingX509ExtendedTrustManager.java:81) ~[netty-handler-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.handler.ssl.ReferenceCountedOpenSslClientContext$ExtendedTrustManagerVerifyCallback.verify(ReferenceCountedOpenSslClientContext.java:245) ~[netty-handler-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.handler.ssl.ReferenceCountedOpenSslContext$AbstractCertificateVerifier.verify(ReferenceCountedOpenSslContext.java:898) ~[netty-handler-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.internal.tcnative.CertificateVerifierTask.runTask(CertificateVerifierTask.java:36) ~[netty-tcnative-classes-2.0.77.Final.jar:2.0.77.Final]
        at io.netty.internal.tcnative.SSLTask.run(SSLTask.java:48) ~[netty-tcnative-classes-2.0.77.Final.jar:2.0.77.Final]
        at io.netty.internal.tcnative.SSLTask.run(SSLTask.java:42) ~[netty-tcnative-classes-2.0.77.Final.jar:2.0.77.Final]
        at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.runAndResetNeedTask(ReferenceCountedOpenSslEngine.java:1555) ~[netty-handler-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.handler.ssl.ReferenceCountedOpenSslEngine.lambda$getDelegatedTask$0(ReferenceCountedOpenSslEngine.java:1583) ~[netty-handler-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.handler.ssl.SslHandler.runDelegatedTasks(SslHandler.java:1692) ~[netty-handler-4.2.15.Final.jar:4.2.15.Final]
        at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1548) ~[netty-handler-4.2.15.Final.jar:4.2.15.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:148) ~[na:na]
        at java.base/sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:129) ~[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:383) ~[na:na]
        ... 35 common frames omitted

Metadata

Metadata

Assignees

No one assigned

    Labels

    duplicateThis issue or pull request already exists

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions