Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 755
Hasan Alp Zengin Avatar asked Oct 16 '19 06:10

Hasan Alp Zengin


2 Answers

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.

like image 68
Planky Avatar answered Sep 20 '22 07:09

Planky


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);
        }
    }
like image 38
Anand Kaushal Avatar answered Sep 21 '22 07:09

Anand Kaushal