Azure-sdk-for-java: Support Spring Boot 2.4 (generally, but especially for AzureAD authentication)

Created on 14 Dec 2020  ·  35Comments  ·  Source: Azure/azure-sdk-for-java

Hi

I'm upgrading my app to Spring Boot 2.4.1 but have unfortunately hit a dependency issue.

I've downgraded the nimbus library to 7.1 as per other posts, but it seems that spring security itself is relying on a different version. There wasn't a ticket about this per-se and keen to see if there's an easy resolution whilst (#17808 or #17986 are being worked on). I wasn't sure that #17808 definitely fixes the problem - but is there a solution in the meantime?

Relevant parts from my pom files:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.1</version>
        <relativePath/>
    </parent>

  <dependency>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>azure-active-directory-spring-boot-starter</artifactId>
            <version>2.3.5</version>
        </dependency>
        <dependency>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>azure-spring-boot-starter</artifactId>
            <version>2.3.5</version>
        </dependency>
        <!-- adding to ensure compatibility -->
        <dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>nimbus-jose-jwt</artifactId>
            <version>7.9</version>
        </dependency>

The app now starts up (didn't without the downgrade to 7.9 nimbus) but it now fails on the authentication/authorisation process:

java.lang.NoSuchMethodError: 'void com.nimbusds.jose.proc.JWSVerificationKeySelector.<init>(java.util.Set, com.nimbusds.jose.jwk.source.JWKSource)'
    at org.springframework.security.oauth2.jwt.NimbusJwtDecoder$JwkSetUriJwtDecoderBuilder.jwsKeySelector(NimbusJwtDecoder.java:333)
    at org.springframework.security.oauth2.jwt.NimbusJwtDecoder$JwkSetUriJwtDecoderBuilder.processor(NimbusJwtDecoder.java:348)
    at org.springframework.security.oauth2.jwt.NimbusJwtDecoder$JwkSetUriJwtDecoderBuilder.build(NimbusJwtDecoder.java:361)
    at org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory.buildDecoder(OidcIdTokenDecoderFactory.java:167)
    at org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory.lambda$createDecoder$3(OidcIdTokenDecoderFactory.java:129)
    at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1705)
    at org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory.createDecoder(OidcIdTokenDecoderFactory.java:128)
    at org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory.createDecoder(OidcIdTokenDecoderFactory.java:66)
    at org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider.createOidcToken(OidcAuthorizationCodeAuthenticationProvider.java:235)
    at org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider.authenticate(OidcAuthorizationCodeAuthenticationProvider.java:154)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182)
    at org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.attemptAuthentication(OAuth2LoginAuthenticationFilter.java:192)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:222)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.doFilterInternal(OAuth2AuthorizationRequestRedirectFilter.java:178)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:141)
    at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:82)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:888)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1597)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.base/java.lang.Thread.run(Thread.java:829)
Client azure-spring azure-spring-aad customer-reported question

Most helpful comment

reactor-core 3.4.3 is now available.

All 35 comments

Hi, @chrisburrell , now the azure-spring-boot-starter does not support spring-boot-2.4.x well, could you please use spring-boot2.3.5 for now?

After https://github.com/Azure/azure-sdk-for-java/pull/17808 merged, we will release a new GA version, then the problem will be fixed.

Next GA version will be released before the end of 2021.01.

@chenrujun as ticket which you mentioned is probably not valid (those versions are incompatible) do you have some workaround for now how to use spring-boot 2.4.x with azure spring boot (we are using currently azure-spring-boot-bom 2.3.5)?

@bilak , we just released 3.0.0, please try this:

       <dependency>
            <groupId>com.azure.spring</groupId>
            <artifactId>azure-spring-boot-starter-active-directory</artifactId>
            <version>3.0.0</version>
        </dependency>

3.0.0 is still work for spring-boot 2.2 - 2.3.

We will release 3.2.0 for spring-boot 2.4.x before the end of this month, you can wait for the release.

Refs: https://repo.maven.apache.org/maven2/com/azure/spring/azure-spring-boot-starter-active-directory/

@chenrujun I've just write to this issue because It has in name general spring boot 2.4 and I want to upgrade to spring boot 2.4. It's shame that I can't, even when spring boot 2.4 is out for more than 2 months :-1:
And it's not only about active directory library.

@bilak

Sorry that we faced some problem when upgrade to spring boot 2.4.0.
Refs: https://github.com/Azure/azure-sdk-for-java/pull/17905

You can choose one of the options:

  1. Try azure-spring-boot-starter-active-directory 3.0.0, check whether it work well with spring-boot 2.4.
  2. Wait for next release. we will release azure-spring-boot-starter-xxx 3.2.0 in this month. which is work for spring-boot 2.4.

Hi,

azure-spring-boot-starter-active-directory 3.0.0 does not work either...
You're my last step to have a production ready reactive have in prod :-)

Best regards,

Hi, @Tcharl .

You're my last step to have a production ready reactive have in prod :-)

Do you mean you are using Web on Reactive Stack? We do not support reactive for our starters now.

However, I succeed to use spring-boot-oauth2-client instead of azad dedicated library (what's the added value of the microsoft one?). App Insight sdk is the last problematic one...

@Tcharl

what's the added value of the microsoft one?

We support other functions like:

  1. Authorize multiple authorizedClient in one time.
  2. Support on-demand consent.
  3. Validate issuer and audience in JWT.
  4. Support on-behalf-of flow.
    etc..

Refs:

  1. https://github.com/Azure/azure-sdk-for-java/tree/master/sdk/spring/azure-spring-boot-starter-active-directory#web-app-authenticate-in-web-app
  2. https://github.com/Azure/azure-sdk-for-java/tree/master/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-webapp
  3. https://github.com/Azure/azure-sdk-for-java/tree/master/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-resource-server

+1 on this. Spring Boot 2.4.2 with Azure SDK results in the following classnotfound exception (oidc version used in Azure is too old):

2021-01-25, 07:30:36 ERROR java.lang.ClassNotFoundException: com.nimbusds.oauth2.sdk.http.CommonContentTypes
    at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:471)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:589)
    at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:151)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
    at com.microsoft.aad.adal4j.AdalTokenRequest.toOAuthRequest(AdalTokenRequest.java:158)
    at com.microsoft.aad.adal4j.AdalTokenRequest.executeOAuthRequestAndProcessResponse(AdalTokenRequest.java:86)
    at com.microsoft.aad.adal4j.AuthenticationContext.acquireTokenCommon(AuthenticationContext.java:930)
    at com.microsoft.aad.adal4j.AcquireTokenCallable.execute(AcquireTokenCallable.java:70)
    at com.microsoft.aad.adal4j.AcquireTokenCallable.execute(AcquireTokenCallable.java:38)
    at com.microsoft.aad.adal4j.AdalCallable.call(AdalCallable.java:47)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)

Please upgrade the following dependencies:

      <dependency>
        <groupId>com.nimbusds</groupId>
        <artifactId>oauth2-oidc-sdk</artifactId>
        <version>${oauth2-oidc-sdk.version}</version>
      </dependency>
      <dependency>
        <groupId>com.nimbusds</groupId>
        <artifactId>nimbus-jose-jwt</artifactId>
        <version>${nimbus-jose-jwt.version}</version>
      </dependency>

Got it working by using the following versions: (both spring oauth2 AND Azure SDK work in this case)

<microsoft-azure.version>1.38.1</microsoft-azure.version>
<nimbus-jose-jwt.version>9.4.1</nimbus-jose-jwt.version>
<oauth2-oidc-sdk.version>7.5</oauth2-oidc-sdk.version>
<spring.boot.version>2.4.2</spring.boot.version>

oauth2-oidc-sdk greater or equal to 8.x doesn't work.

Hope you guys will update this quickly as it's a dirty hack / difficult to maintain to have discrepancies in maven dependencies.

Thanks! You can upgrade to Spring Boot 2.4.2, Nimbus 9.4.1 and oidc 8.34.2. Your "upgraded" dependencies are already out of date.

@jloisel
Thank you for your information.

Your "upgraded" dependencies are already out of date.

They are just patch version update. It's easy to us to update patch version.

@chenrujun any news with release compatible with spring boot 2.4.x?

@bilak

If everything goes well, we will release new version with compatible with spring-boot 2.4.x at the end of this month.

Here are more details:

  1. When we upgrade to spring-boot 2.4.x, we found a bug of netty: https://github.com/reactor/reactor-core/issues/2579
  2. We need wait netty's next release. Here is netty's milestone: https://github.com/reactor/reactor-core/milestone/97?closed=1

reactor-core 3.4.3 is now available.

@jloisel
Thank you for your information.

This month's regular release have finished for some modules in this repo.
To keep the modules use the same version, reactor-core 3.4.x can be used from next month.

So we can not support spring-boot 2.4.x until next month's regular release.
Please wait.
Sorry for bring you trouble.

@chenrujun so we have next month, what is the status please? Are we going to wait next mont? :D

@bilak
Current plan is that we will release azure-spring-boot-starter-active-directory:3.3.0 before 2021-03-11, which will work with spring-boot:2.4.3

@chenrujun what about other libraries? azure-spring-boot-bom mainly.

@bilak other libraries will be released, too. For spring-boot 2.4.

Hey @chenrujun, are you folks still on target for a March release or are we looking at mid-April now?

@raniemi , on March release.

Closing this issue because 3.3.0 is released, which support spring-boot:2.4.3.

This is not solved as other azure artifacts rely on adal4j:
https://mvnrepository.com/artifact/com.microsoft.azure/adal4j/1.6.6

Which in turn is still depending on an old version of oauth2-oidc-sdk. There is still a dependency conflict if you try to use Azure client within a spring boot application.

@jloisel

Sorry I can't get your point.
Could you please check our sample project?

  1. It does not depends on adal4j:
    image

  2. There is no conflict about oauth2-oidc-sdk:
    image

  3. Same to nimbus-jose-jwt:
    image

Standalone Azure client still does depend on it:
https://mvnrepository.com/artifact/com.microsoft.azure/adal4j/1.6.4/usages?p=1

We're using Azure client:

      <dependency>
        <groupId>com.microsoft.azure</groupId>
        <artifactId>azure</artifactId>
        <version>1.40.0</version>
      </dependency>

And it still transitively depends on adal4j, which in turn depends on old versions of oidc and nimbus.

A simple search shows there is still some code depending on adal4j in various locations:
https://github.com/Azure/azure-sdk-for-java/search?q=adal4j

I've spend some time trying to understand which dependency to use to start / stop vms on Azure, but it's really puzzling given how many artifacts there are. The migration guide doesn't explicit which dependencies to use either, it just contains some code.

EDIT: Found some samples:
https://github.com/Azure-Samples/compute-java-manage-vm/blob/master/src/main/java/com/azure/resourcemanager/compute/samples/ManageVirtualMachine.java

Seems like we need to use client 2.x.

Hi, @jloisel

Why are you using com.microsoft.azure:azure? I mean what do you use it for?
I think you should use com.azure.resourcemanager:xxx.

Seems like we need to use client 2.x.

Do you mean now you want to use com.azure.resourcemanager:xxx?

Hi,

We thought it was the right client to use, but we were wrong. I have migrated our codebase to use azure-resourcemanager-compute along with azure-identity.

We use Azure client to start / stop instances on Azure automatically based on our client needs.

We use Azure client to start / stop instances on Azure automatically based on our client needs.

Hi, @saragluna .
IMU,
azure is track 1 library,
we should avoid using track 1 library.

Do we have track 2 library to start / stop instances on Azure?

@jloisel

We use Azure client to start / stop instances on Azure

Is the Azure client you referred to the azure-resourcemanager-compute library?

We used track 1 library until now. That's why we had issues with nimbus / oidc conflicts in Spring boot:

@Override
  public AzureClient newClient(final AzureAccount account) {
    final ApplicationTokenCredentials credentials = new ApplicationTokenCredentials(
      account.getClientId(),
      account.getTenantId(),
      account.getClientSecret(),
      ENVS.getOrDefault(account.getEnvironment(), AZURE));

    final Authenticated auth = Azure.authenticate(credentials);

    final Azure c = auth.withSubscription(account.getSubscriptionId());
    return new ImmutableAzureClient(
      c.disks(),
      c.networks(),
      c.virtualMachines(),
      c.publicIPAddresses(),
      c.networkInterfaces(),
      c.galleries()
    );

Now we use track 2 client:

@Override
  public AzureClient newClient(final AzureAccount acc) {
    final TokenCredential credential = new ClientSecretCredentialBuilder()
      .clientId(acc.getClientId())
      .clientSecret(acc.getClientSecret())
      .tenantId(acc.getTenantId())
      .build();
    final AzureEnvironment env = ENVS.getOrDefault(acc.getEnvironment(), AZURE);
    final AzureProfile profile = new AzureProfile(acc.getTenantId(), acc.getSubscriptionId(), env);

    final ComputeManager compute = ComputeManager
      .configure()
      .withLogLevel(BASIC)
      .authenticate(credential, profile);

    final NetworkManager network = NetworkManager
      .configure()
      .withLogLevel(BASIC)
      .authenticate(credential, profile);

    return new ImmutableAzureClient(
      compute.disks(),
      network.networks(),
      compute.virtualMachines(),
      network.publicIpAddresses(),
      network.networkInterfaces(),
      compute.galleries()
    );

We switched from using azure artifact to azure-resourcemanager-compute library.

@jloisel
After you use track 2 library, do you have any problem?

It's working fine now. :+1:

Was this page helpful?
0 / 5 - 0 ratings