Spring Boot Azure AD Bearer Header Authentication (Signed JWT rejected: Invalid signature)
Asked Answered
A

3

5

I have an GraphQL API written with Spring Boot. I wanted to connect it with Azure Active Directory but getting that error when I sent a request filled with Authentication bearer header.

com.nimbusds.jose.proc.BadJWSException: Signed JWT rejected: Invalid signature

I'm using Azure Active Directory Starter and here is my setup:

Web Security Configurer:

import com.microsoft.azure.spring.autoconfigure.aad.AADAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class ADConfigurerAdapter extends WebSecurityConfigurerAdapter {

    @Autowired
    private AADAuthenticationFilter aadAuthenticationFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.headers().frameOptions().disable();

        http.addFilterBefore(aadAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                .csrf().disable()
                .authorizeRequests().antMatchers("/api").hasAnyRole("developer")
                .and()
                .authorizeRequests().antMatchers("/").permitAll()
                .and()
                .authorizeRequests().anyRequest().permitAll()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
}

Getting the bearer token from MSAL-AngularJS front-end:

app.config(['msalAuthenticationServiceProvider', '$locationProvider', (msalProvider, $locationProvider)=>{

    msalProvider.init({
        clientID: CLIENT_ID,
        authority: 'https://login.microsoftonline.com/'+TENANT,
        validateAuthority: false,
        tokenReceivedCallback: function (errorDesc, token, error, tokenType) {
        },
        optionalParams: {
            cacheLocation: 'localStorage',
            storeAuthStateInCookie: true,
            logger: logger,
            endPoints: endpointsMap
        },
        routeProtectionConfig: {
            popUp: true
        },
    });

    $locationProvider.html5Mode(false).hashPrefix('');

}]);

...
app.controller("NavController", ['$scope', '$rootScope', '$window', 'msalAuthenticationService', ($scope, $rootScope, $window, msalService)=>{

    $scope.$evalAsync(()=>{
        $scope.userInfo = msalService.userInfo;

        if($scope.userInfo.isAuthenticated){
            msalService.acquireTokenSilent(['user.read']).then(token=>{
                $rootScope.authToken = token;
                $rootScope.$broadcast("authTokenSet");
            }).catch(error=>{
                console.log("Error: ",error)
            });
        }
    })
...

I also tested it manually via Postman with returned token from acquireTokenSilent request.

Here is error message:

com.nimbusds.jose.proc.BadJWSException: Signed JWT rejected: Invalid signature
    at com.nimbusds.jwt.proc.DefaultJWTProcessor.<clinit>(DefaultJWTProcessor.java:103) ~[nimbus-jose-jwt-7.9.jar:7.9]
    at com.microsoft.azure.spring.autoconfigure.aad.UserPrincipalManager.getAadJwtTokenValidator(UserPrincipalManager.java:91) ~[azure-spring-boot-2.1.7.jar:na]
    at com.microsoft.azure.spring.autoconfigure.aad.UserPrincipalManager.buildUserPrincipal(UserPrincipalManager.java:82) ~[azure-spring-boot-2.1.7.jar:na]
    at com.microsoft.azure.spring.autoconfigure.aad.AADAuthenticationFilter.doFilterInternal(AADAuthenticationFilter.java:78) ~[azure-spring-boot-2.1.7.jar:na]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:74) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) ~[spring-security-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:114) ~[spring-boot-actuator-2.1.8.RELEASE.jar:2.1.8.RELEASE]
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:104) ~[spring-boot-actuator-2.1.8.RELEASE.jar:2.1.8.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526) [tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:860) [tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1587) [tomcat-embed-core-9.0.24.jar:9.0.24]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.24.jar:9.0.24]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_222]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_222]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.24.jar:9.0.24]
    at java.lang.Thread.run(Thread.java:748) [na:1.8.0_222]

I couldn't find much documentation about that.

Artificer answered 16/10, 2019 at 6:13 Comment(1)
I have very similar problem. The reason is that the AAD responds with JWT for v1.0 endpoints. Have a look at ver claim. Despite that your endpoints are v2.0, the JWT is v1.0 for unknown reason.Averell
R
3

Use the latest milestone release of the azure-spring-boot library (2.2.0.M1) to get past this error.

This error shows up when you're using the v2.0 endpoints to get your token (which MSAL does) and also using the latest azure-spring-boot library (2.1.7 at the time of writing).

The key difference is that the v2.0 endpoints return a different issuer claim inside of the JWT for id_tokens than the v1.0 endpoints

  • v1.0 claim: "iss": "https://sts.windows.net/{tenant_id}/"
  • v2.0 claim: "iss": "https://login.microsoftonline.com/{tenant_id}/v2.0"

If you look at the code where your error is getting thrown, you'll see that it is doing an explicit check for issuers to ensure that the token it is about to use actually came from Microsoft. See: https://github.com/microsoft/azure-spring-boot/blob/2.1.7/azure-spring-boot/src/main/java/com/microsoft/azure/spring/autoconfigure/aad/UserPrincipalManager.java#L102

However, in 2.2.0.M1 the check is expanded to include login.microsoftonline.com and so the token passes through this check successfully.

Rhubarb answered 18/10, 2019 at 16:50 Comment(0)
B
3

The above problem is resolved using below steps.

1) Change the azure spring boot api to 2.2.0M1 version

<dependency>
        <groupId>com.microsoft.azure</groupId>
        <artifactId>azure-spring-boot</artifactId>
        <version>2.2.0.M1</version>
    </dependency>

2) In your web config filter simply autowire the AADAppRoleStatelessAuthenticationFilter

@Autowired private AADAppRoleStatelessAuthenticationFilter aadAuthFilter;

3) Add an entry into application.properties file of your spring boot application

    azure.activedirectory.session-stateless=true

below is the complete filter code

    package com.staples.dmp.web;

    import java.util.Collection;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

    import com.microsoft.azure.spring.autoconfigure.aad.AADAppRoleStatelessAuthenticationFilter;


    @SuppressWarnings("unused")
    @EnableGlobalMethodSecurity(securedEnabled = true,
    prePostEnabled = true)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

        @Autowired
        private AADAppRoleStatelessAuthenticationFilter aadAuthFilter;


       @Override
        protected void configure(HttpSecurity http) throws Exception {


            // require OAuth JWT token from AzureAD app for /api
            http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/v1/**").permitAll()
            .antMatchers("/datascience/v1/ds_batch").permitAll()
            .antMatchers("/v2/ad/**").hasAnyRole("USER")
            .anyRequest().authenticated();

            // adding userid Password authentication filter to the filter chain
            http.addFilterBefore(aadAuthFilter,         UsernamePasswordAuthenticationFilter.class);
        }
    }
Blatant answered 17/12, 2019 at 4:47 Comment(0)
A
0

Downgraded the version of Azure Active Directory Starter Library. After that it worked with active sign-in. Changed the front-end library from MSAL to ADAL and header authentication also worked somehow.

Artificer answered 17/10, 2019 at 17:56 Comment(1)
Keep in mind that ADAL will be deprecated soon. I would highly recommend find the solution using MSAL. If using ADAL worked, then the problem could be the usage of v2 endpoint. Maybe you just needed to change 'https://login.microsoftonline.com/'+TENANT+'/v2.0'Allwein

© 2022 - 2024 — McMap. All rights reserved.