-
Notifications
You must be signed in to change notification settings - Fork 2.2k
validate access token #17613
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
validate access token #17613
Changes from 8 commits
29b5cd0
081cb82
c8a3508
b9aa067
fdfb007
bddc2e4
cb82a9b
0732746
3f8dac0
4b6e974
ce65efe
05d929d
4e4d053
b362dfb
ae39fe7
acaeefa
d25e9df
a31f1a6
f7aa628
0851fc6
0ac20f9
9f80db0
fd2155b
7403602
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
| package com.azure.spring.autoconfigure.aad; | ||
|
|
||
| import java.util.List; | ||
| import org.springframework.security.oauth2.core.OAuth2TokenValidator; | ||
| import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; | ||
| import org.springframework.security.oauth2.jwt.Jwt; | ||
| import org.springframework.security.oauth2.jwt.JwtClaimValidator; | ||
| import org.springframework.util.Assert; | ||
|
|
||
| /** | ||
| * Validates the "aud" claim in a {@link Jwt}, that is matches a configured value | ||
| */ | ||
| public class AADJwtAudienceValidator implements OAuth2TokenValidator<Jwt> { | ||
|
|
||
| private final JwtClaimValidator<List<String>> validator; | ||
|
|
||
| /** | ||
| * Constructs a {@link AADJwtAudienceValidator} using the provided parameters | ||
| * | ||
| * @param audiences - The audience that each {@link Jwt} should have. | ||
| */ | ||
| @SuppressWarnings({"unchecked", "rawtypes"}) | ||
| public AADJwtAudienceValidator(List<String> audiences) { | ||
| Assert.notNull(audiences, "audiences cannot be null"); | ||
| this.validator = new JwtClaimValidator(AADTokenClaim.AUD, aud -> audiences.containsAll((List<String>) aud)); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| @Override | ||
| public OAuth2TokenValidatorResult validate(Jwt token) { | ||
| Assert.notNull(token, "token cannot be null"); | ||
| return this.validator.validate(token); | ||
| } | ||
|
|
||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
| package com.azure.spring.autoconfigure.aad; | ||
|
|
||
| import java.util.function.Predicate; | ||
| import org.springframework.security.oauth2.core.OAuth2TokenValidator; | ||
| import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; | ||
| import org.springframework.security.oauth2.jwt.Jwt; | ||
| import org.springframework.security.oauth2.jwt.JwtClaimValidator; | ||
| import org.springframework.util.Assert; | ||
|
|
||
| /** | ||
| * Validates the "iss" claim in a {@link Jwt}, that is matches a configured value | ||
| */ | ||
| public class AADJwtIssuerValidator implements OAuth2TokenValidator<Jwt> { | ||
|
|
||
| private static final String LOGIN_MICROSOFT_ONLINE_ISSUER = "https://login.microsoftonline.com/"; | ||
| private static final String STS_WINDOWS_ISSUER = "https://sts.windows.net/"; | ||
| private static final String STS_CHINA_CLOUD_API_ISSUER = "https://sts.chinacloudapi.cn/"; | ||
| private final JwtClaimValidator<String> validator; | ||
|
|
||
| /** | ||
| * Constructs a {@link AADJwtIssuerValidator} using the provided parameters | ||
| */ | ||
| @SuppressWarnings({"unchecked", "rawtypes"}) | ||
| public AADJwtIssuerValidator() { | ||
| this.validator = new JwtClaimValidator(AADTokenClaim.ISS, validIssuer()); | ||
| } | ||
|
|
||
| private Predicate<String> validIssuer() { | ||
| return iss -> { | ||
| if (iss == null) { | ||
| return false; | ||
| } | ||
| return iss.startsWith(LOGIN_MICROSOFT_ONLINE_ISSUER) | ||
| || iss.startsWith(STS_WINDOWS_ISSUER) | ||
| || iss.startsWith(STS_CHINA_CLOUD_API_ISSUER); | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| @Override | ||
| public OAuth2TokenValidatorResult validate(Jwt token) { | ||
| Assert.notNull(token, "token cannot be null"); | ||
| return this.validator.validate(token); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
| package com.azure.spring.autoconfigure.aad; | ||
|
|
||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; | ||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; | ||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||
| import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; | ||
| import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; | ||
| import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; | ||
| import org.springframework.security.oauth2.core.OAuth2TokenValidator; | ||
| import org.springframework.security.oauth2.jwt.Jwt; | ||
| import org.springframework.security.oauth2.jwt.JwtDecoder; | ||
| import org.springframework.security.oauth2.jwt.JwtTimestampValidator; | ||
| import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; | ||
| import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; | ||
| import org.springframework.util.StringUtils; | ||
|
|
||
| /** | ||
| * <p> | ||
| * The configuration will not be activated if no {@link BearerTokenAuthenticationToken} class provided. | ||
| * <p> | ||
| * By default, creating a JwtDecoder through JwkKeySetUri will be auto-configured. | ||
| */ | ||
| @Configuration(proxyBeanMethods = false) | ||
| @EnableConfigurationProperties({AADAuthenticationProperties.class, ServiceEndpointsProperties.class}) | ||
| @ConditionalOnClass(BearerTokenAuthenticationToken.class) | ||
| public class AADResourceServerAutoConfiguration { | ||
|
|
||
| @Autowired | ||
| private AADAuthenticationProperties aadAuthenticationProperties; | ||
| @Autowired | ||
| private ServiceEndpointsProperties serviceEndpointsProperties; | ||
|
|
||
| /** | ||
| * Use JwkKeySetUri to create JwtDecoder | ||
| * | ||
| * @return JwtDecoder bean | ||
| */ | ||
| @Bean | ||
| @ConditionalOnMissingBean(JwtDecoder.class) | ||
| public JwtDecoder jwtDecoderByJwkKeySetUri() { | ||
| if (StringUtils.isEmpty(aadAuthenticationProperties.getTenantId())) { | ||
| aadAuthenticationProperties.setTenantId("common"); | ||
| } | ||
| String authorizationServerUri = | ||
| serviceEndpointsProperties.getServiceEndpoints(aadAuthenticationProperties.getEnvironment()) | ||
| .getAadSigninUri(); | ||
| AuthorizationServerEndpoints endpoints = new AuthorizationServerEndpoints(authorizationServerUri); | ||
| NimbusJwtDecoder nimbusJwtDecoder = NimbusJwtDecoder | ||
| .withJwkSetUri(endpoints.jwkSetEndpoint(aadAuthenticationProperties.getTenantId())).build(); | ||
| List<OAuth2TokenValidator<Jwt>> validators = createDefaultValidator(); | ||
| nimbusJwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(validators)); | ||
| return nimbusJwtDecoder; | ||
| } | ||
|
|
||
| public List<OAuth2TokenValidator<Jwt>> createDefaultValidator() { | ||
| List<OAuth2TokenValidator<Jwt>> validators = new ArrayList<>(); | ||
| if (!StringUtils.isEmpty(aadAuthenticationProperties.getAppIdUri())) { | ||
| List<String> validAudiences = new ArrayList<>(); | ||
| validAudiences.add(aadAuthenticationProperties.getAppIdUri()); | ||
| validators.add(new AADJwtAudienceValidator(validAudiences)); | ||
| } | ||
| validators.add(new AADJwtIssuerValidator()); | ||
| validators.add(new JwtTimestampValidator()); | ||
| return validators; | ||
| } | ||
|
|
||
| /** | ||
| * Default configuration class for using AAD authentication and authorization. User can write another configuration | ||
| * bean to override it. | ||
| */ | ||
| @Configuration | ||
| @ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class) | ||
| @EnableWebSecurity | ||
| public static class DefaultAzureOAuth2ResourceServerWebSecurityConfigurerAdapter extends | ||
| WebSecurityConfigurerAdapter { | ||
|
|
||
| @Override | ||
| protected void configure(HttpSecurity http) throws Exception { | ||
| http | ||
| .authorizeRequests((requests) -> requests.anyRequest().authenticated()) | ||
| .oauth2ResourceServer() | ||
| .jwt() | ||
| .jwtAuthenticationConverter(new AzureJwtBearerTokenAuthenticationConverter()); | ||
| } | ||
| } | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| package com.azure.spring.autoconfigure.aad; | ||
|
|
||
| import com.nimbusds.jose.JWSObject; | ||
| import com.nimbusds.jwt.JWTClaimsSet; | ||
| import com.nimbusds.jwt.JWTClaimsSet.Builder; | ||
| import java.text.ParseException; | ||
| import java.util.Collection; | ||
| import java.util.Map; | ||
| import java.util.Map.Entry; | ||
| import org.springframework.core.convert.converter.Converter; | ||
| import org.springframework.security.authentication.AbstractAuthenticationToken; | ||
| import org.springframework.security.core.GrantedAuthority; | ||
| import org.springframework.security.oauth2.core.OAuth2AccessToken; | ||
| import org.springframework.security.oauth2.core.OAuth2AccessToken.TokenType; | ||
| import org.springframework.security.oauth2.jwt.Jwt; | ||
| import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; | ||
| import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; | ||
|
|
||
| /** | ||
| * A {@link Converter} that takes a {@link Jwt} and converts it into a {@link PreAuthenticatedAuthenticationToken}. | ||
| */ | ||
| public class AzureJwtBearerTokenAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> { | ||
|
|
||
| private final JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); | ||
|
|
||
| public AzureJwtBearerTokenAuthenticationConverter() { | ||
| } | ||
|
|
||
| public AbstractAuthenticationToken convert(Jwt jwt) { | ||
| AbstractAuthenticationToken token = this.jwtAuthenticationConverter.convert(jwt); | ||
| Collection<GrantedAuthority> authorities = token.getAuthorities(); | ||
|
saragluna marked this conversation as resolved.
Outdated
|
||
| OAuth2AccessToken accessToken = new OAuth2AccessToken(TokenType.BEARER, jwt.getTokenValue(), jwt.getIssuedAt(), | ||
| jwt.getExpiresAt()); | ||
| Map<String, Object> attributes = jwt.getClaims(); | ||
| JWTClaimsSet.Builder builder = new Builder(); | ||
| for (Entry<String, Object> entry : attributes.entrySet()) { | ||
| builder.claim(entry.getKey(), entry.getValue()); | ||
| } | ||
| JWSObject jwsObject = null; | ||
| try { | ||
| jwsObject = JWSObject.parse(accessToken.getTokenValue()); | ||
| } catch (ParseException e) { | ||
| e.printStackTrace(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. need any log info here?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we throw this exception?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should have a log here. |
||
| } | ||
| UserPrincipal userPrincipal = new UserPrincipal(accessToken.getTokenValue(), jwsObject, builder.build()); | ||
| return new PreAuthenticatedAuthenticationToken(userPrincipal, null, authorities); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| package com.azure.spring.autoconfigure.aad; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
| import static org.mockito.Mockito.mock; | ||
| import static org.mockito.Mockito.when; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import org.junit.Test; | ||
| import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; | ||
| import org.springframework.security.oauth2.jwt.Jwt; | ||
|
|
||
| public class AADJwtAudienceValidatorTest { | ||
|
|
||
| final AADAuthenticationProperties aadAuthenticationProperties = mock(AADAuthenticationProperties.class); | ||
| final Jwt jwt = mock(Jwt.class); | ||
| final List<String> audiences = new ArrayList<>(); | ||
| final List<String> claimAudience = new ArrayList<>(); | ||
|
|
||
| @Test | ||
| public void testClientIdExistAndSuccessVerify() { | ||
| when(aadAuthenticationProperties.getClientId()).thenReturn("fake-client-id"); | ||
| when(jwt.getClaim(AADTokenClaim.AUD)).thenReturn(claimAudience); | ||
| claimAudience.add("fake-client-id"); | ||
| audiences.add(aadAuthenticationProperties.getClientId()); | ||
| AADJwtAudienceValidator aadJwtAudienceValidator = new AADJwtAudienceValidator(audiences); | ||
| OAuth2TokenValidatorResult result = aadJwtAudienceValidator.validate(jwt); | ||
|
|
||
| assertThat(result).isNotNull(); | ||
| assertThat(result.getErrors()).isEmpty(); | ||
| } | ||
|
|
||
| @Test | ||
| public void testAppIdUriExistAndSuccessVerify() { | ||
| when(aadAuthenticationProperties.getClientId()).thenReturn("fake-app-id-uri"); | ||
| when(jwt.getClaim(AADTokenClaim.AUD)).thenReturn(claimAudience); | ||
| claimAudience.add("fake-app-id-uri"); | ||
| audiences.add(aadAuthenticationProperties.getClientId()); | ||
| AADJwtAudienceValidator aadJwtAudienceValidator = new AADJwtAudienceValidator(audiences); | ||
| OAuth2TokenValidatorResult result = aadJwtAudienceValidator.validate(jwt); | ||
|
|
||
| assertThat(result).isNotNull(); | ||
| assertThat(result.getErrors()).isEmpty(); | ||
| } | ||
|
|
||
| @Test | ||
| public void testAppIdUriExistAndClientIdAndSuccessVerify() { | ||
| when(aadAuthenticationProperties.getClientId()).thenReturn("fake-app-id-uri"); | ||
| when(aadAuthenticationProperties.getAppIdUri()).thenReturn("fake-client-id"); | ||
| when(jwt.getClaim(AADTokenClaim.AUD)).thenReturn(claimAudience); | ||
| //claimAudience.add("fake-client-id"); | ||
| claimAudience.add("fake-app-id-uri"); | ||
| audiences.add(aadAuthenticationProperties.getClientId()); | ||
| audiences.add(aadAuthenticationProperties.getAppIdUri()); | ||
| AADJwtAudienceValidator aadJwtAudienceValidator = new AADJwtAudienceValidator(audiences); | ||
| OAuth2TokenValidatorResult result = aadJwtAudienceValidator.validate(jwt); | ||
|
|
||
| assertThat(result).isNotNull(); | ||
| assertThat(result.getErrors()).isEmpty(); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.