microsoft / azure-spring-boot

Spring Boot Starters for Azure services
MIT License
374 stars 460 forks source link

BadJWSException: Signed JWT rejected: Invalid signature #857

Closed desertTown closed 4 years ago

desertTown commented 4 years ago

Environment

Summary

Hi, I am follow this sample project and try to test authorize code flow base role https://github.com/microsoft/azure-spring-boot/tree/master/azure-spring-boot-samples/azure-active-directory-v2-spring-boot-backend-sample

I have follow the README.md and replace the application.properties using v2.0 url

spring.security.oauth2.client.registration.azure.client-id={{client-id}}
spring.security.oauth2.client.registration.azure.client-secret={{client-secret}}
spring.security.oauth2.client.registration.azure.client-name=Azure
spring.security.oauth2.client.registration.azure.provider=azure-oauth-provider
spring.security.oauth2.client.registration.azure.scope=openid, profile, offline_access, https://graph.microsoft.com/user.read
#spring.security.oauth2.client.registration.azure.redirect-uri-template={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.azure.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.azure.client-authentication-method=basic
spring.security.oauth2.client.registration.azure.authorization-grant-type=authorization_code

spring.security.oauth2.client.provider.azure-oauth-provider.authorization-uri=https://login.microsoftonline.com/{{tenantId}}/oauth2/v2.0/authorize
spring.security.oauth2.client.provider.azure-oauth-provider.token-uri=https://login.microsoftonline.com/{{tenantId}}/oauth2/v2.0/token
spring.security.oauth2.client.provider.azure-oauth-provider.user-info-uri=https://graph.microsoft.com/oidc/userinfo
spring.security.oauth2.client.provider.azure-oauth-provider.jwk-set-uri=https://login.microsoftonline.com/{{tenantId}}/discovery/v2.0/keys
spring.security.oauth2.client.provider.azure-oauth-provider.user-name-attribute=name
spring.security.oauth2.client.provider.azure-oauth-provider.issuer-uri=https://login.microsoftonline.com/{{tenantId}}/v2.0

azure.activedirectory.tenant-id={{tenantId}}
azure.activedirectory.active-directory-groups={{groupName}}
logging.level.org.springframework.*=DEBUG
### stateless (in order to using AADAppRoleStatelessAuthenticationFilter)
azure.activedirectory.session-stateless=true
azure.activedirectory.client-id={{client_id}}

after login, I can acquire acessToken and refreshToken successfully.

but once I using VueClient with acessToken as Authentication header to call the SpringBoot resource API. Filter 'AADAppRoleStatelessAuthenticationFilter' will try to check the acessToken in header. and it will throw Exception

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 org.springframework.security.oauth2.jwt.NimbusJwtDecoder$JwkSetUriJwtDecoderBuilder.processor(NimbusJwtDecoder.java:283) ~[spring-security-oauth2-jose-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.oauth2.jwt.NimbusJwtDecoder$JwkSetUriJwtDecoderBuilder.build(NimbusJwtDecoder.java:298) ~[spring-security-oauth2-jose-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory.buildDecoder(OidcIdTokenDecoderFactory.java:156) ~[spring-security-oauth2-client-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory.lambda$createDecoder$3(OidcIdTokenDecoderFactory.java:119) ~[spring-security-oauth2-client-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660) ~[na:1.8.0_121]
    at org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory.createDecoder(OidcIdTokenDecoderFactory.java:118) ~[spring-security-oauth2-client-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory.createDecoder(OidcIdTokenDecoderFactory.java:66) ~[spring-security-oauth2-client-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider.createOidcToken(OidcAuthorizationCodeAuthenticationProvider.java:226) ~[spring-security-oauth2-client-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider.authenticate(OidcAuthorizationCodeAuthenticationProvider.java:161) ~[spring-security-oauth2-client-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:175) ~[spring-security-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.attemptAuthentication(OAuth2LoginAuthenticationFilter.java:185) ~[spring-security-oauth2-client-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.doFilterInternal(OAuth2AuthorizationRequestRedirectFilter.java:160) ~[spring-security-oauth2-client-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:92) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:92) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:77) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) ~[spring-security-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at 

this acessToken will work on call graph API. etc:

GET https://graph.microsoft.com/oidc/userinfo
Authorization: Bearer {{acessToken}}

could you please explain what reason may cause this Exception "BadJWSException: Signed JWT rejected: Invalid signature"

and one more question: this AADAppRoleStatelessAuthenticationFilter can work with acessToken? or it just only for id_Token?

Reproduce steps

  1. start up springBoot and Vue in different port
  2. using Vue to call springBoot Resource and and it will redirect to AAD loginURL
  3. login successfully and response AcessToken and RefreshToken to Vue Client
  4. Vue Client carry acessToken to call the SpringBoot ResourceServer API

Expected Results

should call SpringBoot resource API successfully

Actual Results

Exception "BadJWSException: Signed JWT rejected: Invalid signature" occur.

saragluna commented 4 years ago

Sorry for the late response. AADAppRoleStatelessAuthenticationFilter only accepts ID token as far as I know.

saragluna commented 4 years ago

I was not accurate about my last comment.

The Invalid signature is probably caused by trying to decode an access token which is issued for accessing Microsoft Graph. You could check the token on jwt.ms or jwt.io to check the audience. If you get "aud": "https://graph.microsoft.com" then such access token should not be passed to AADAppRoleStatelessAuthenticationFilter.

If your front end is the same application as the backend application, you could pass the ID token to the backend. And if not, you could request an access token for your application with your app's scope.

saragluna commented 4 years ago

You could also check this issue.

desertTown commented 4 years ago

@saragluna yes, you're right. if the "aud" is "https://graph.microsoft.com", I cannot pass the AADAppRoleStatelessAuthenticationFilter, After I using refresh token to reget the accessToken in different scope, the JWT return with aud": "api://{{my-clientId}}", this token can pass the AADAppRoleStatelessAuthenticationFilter

Thanks for your reply