-
Notifications
You must be signed in to change notification settings - Fork 2.2k
validate access token #17566
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 #17566
Changes from 1 commit
f8f2940
65b9871
5262e66
88e5368
559a8c1
91d4dc7
1eaa75d
7942159
70d49f4
7f0b710
fca48d1
af9071d
ca6ea29
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,65 @@ | ||
| package com.azure.spring.aad.resource; | ||
|
|
||
|
|
||
| import com.azure.spring.aad.implementation.IdentityEndpoints; | ||
| import com.azure.spring.aad.resource.validator.AzureJwtAudienceValidator; | ||
| import com.azure.spring.aad.resource.validator.AzureJwtIssuerValidator; | ||
| import com.azure.spring.aad.resource.validator.AzureJwtTenantValidator; | ||
| import com.azure.spring.autoconfigure.aad.AADAuthenticationProperties; | ||
| 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.autoconfigure.condition.ConditionalOnProperty; | ||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; | ||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| 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; | ||
|
|
||
| @Configuration(proxyBeanMethods = false) | ||
| @EnableConfigurationProperties(AADAuthenticationProperties.class) | ||
| @ConditionalOnClass(name = {"org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken"}) | ||
| @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) | ||
| @ConditionalOnProperty(prefix = "azure.activedirectory", value = {"client-id", "client-secret", "tenant-id"}) | ||
| public class AzureResourceServerAutoConfiguration { | ||
|
|
||
| @Autowired | ||
| private AADAuthenticationProperties aadAuthenticationProperties; | ||
|
|
||
| @Bean | ||
| @ConditionalOnProperty(prefix = "azure.activedirectory.resource", value = {"client-id", "tenant-id", | ||
|
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. All these properties are not required right? If tenant id is provided, we will construct the jwkset uri from the tenant id, otherwise we could use common?
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.
Yes, you are right. |
||
| "app-id-uri"}) | ||
| @ConditionalOnMissingBean(JwtDecoder.class) | ||
| JwtDecoder jwtDecoderByJwkKeySetUri() { | ||
| IdentityEndpoints endpoints = new IdentityEndpoints(aadAuthenticationProperties.getUri()); | ||
| NimbusJwtDecoder nimbusJwtDecoder = NimbusJwtDecoder | ||
|
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. I see why the tenant id is required here, but why this prefix
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. So for the case of single-tenant app, the tenant id will be its tenant id. For multi-tenant app, it will be common?
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. about |
||
| .withJwkSetUri(endpoints.jwkSetEndpoint(aadAuthenticationProperties.getTenantId())).build(); | ||
| List<OAuth2TokenValidator<Jwt>> validators = createDefaultValidator(); | ||
| nimbusJwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(validators)); | ||
| return nimbusJwtDecoder; | ||
| } | ||
|
|
||
| //TODO There can be other jwtDecoder generation methods | ||
|
|
||
| private List<OAuth2TokenValidator<Jwt>> createDefaultValidator() { | ||
| List<OAuth2TokenValidator<Jwt>> validators = new ArrayList<>(); | ||
| List<String> validAudiences = new ArrayList<>(); | ||
| validAudiences.add(aadAuthenticationProperties.getClientId()); | ||
| validAudiences.add(aadAuthenticationProperties.getAppIdUri()); | ||
| validators.add(new AzureJwtIssuerValidator(aadAuthenticationProperties.getTenantId(), | ||
|
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. Why are we using tenant ids to construct the issuer validator?
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. Because the issuer in the form of "https://sts.windows.net/4acebda4-c077-434a-8675-c4b5afebc8da/", so I hope that by passing tenantId to construct corresponding issuer, then the access token of the 'iss' of claims to match. |
||
| aadAuthenticationProperties.getAllowedTenantIds())); | ||
| validators.add(new AzureJwtAudienceValidator(validAudiences)); | ||
| validators.add(new AzureJwtTenantValidator(aadAuthenticationProperties.getTenantId(), | ||
| aadAuthenticationProperties.getAllowedTenantIds())); | ||
| validators.add(new JwtTimestampValidator()); | ||
| return validators; | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package com.azure.spring.aad.resource.validator; | ||
|
|
||
| import com.azure.spring.autoconfigure.aad.AADTokenClaim; | ||
| 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; | ||
|
|
||
| public class AzureJwtAudienceValidator implements OAuth2TokenValidator<Jwt> { | ||
|
|
||
|
|
||
| private final JwtClaimValidator<List<String>> validator; | ||
|
|
||
| public AzureJwtAudienceValidator(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,68 @@ | ||
| package com.azure.spring.aad.resource.validator; | ||
|
|
||
| import com.azure.spring.autoconfigure.aad.AADTokenClaim; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
| 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; | ||
| import org.springframework.util.StringUtils; | ||
|
|
||
| public class AzureJwtIssuerValidator 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; | ||
|
|
||
| public AzureJwtIssuerValidator(String tenantId, Set<String> allowedTenantIds) { | ||
| Assert.notNull(tenantId, "tenantId cannot be null"); | ||
| Assert.notNull(allowedTenantIds, "allowedTenantIds cannot be null"); | ||
| if (allowedTenantIds.isEmpty()) { | ||
| if (StringUtils.isEmpty(tenantId)) { | ||
| this.validator = new JwtClaimValidator(AADTokenClaim.ISS, iss -> true); | ||
| } else { | ||
| List<String> issuers = assembleIssuer(tenantId); | ||
| this.validator = new JwtClaimValidator(AADTokenClaim.ISS, issuers::contains); | ||
| } | ||
| } else { | ||
| this.validator = new JwtClaimValidator(AADTokenClaim.ISS, validIssuer(allowedTenantIds)); | ||
| } | ||
| } | ||
|
|
||
| private List<String> assembleIssuer(String tenantId) { | ||
| List<String> issuers = new ArrayList<>(); | ||
| issuers.add(LOGIN_MICROSOFT_ONLINE_ISSUER + tenantId + "/"); | ||
|
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. Do we need to check the tenant in issuer claim in this validator? Which doc are we following here? |
||
| issuers.add(STS_WINDOWS_ISSUER + tenantId + "/"); | ||
| issuers.add(STS_CHINA_CLOUD_API_ISSUER + tenantId + "/"); | ||
| return issuers; | ||
| } | ||
|
|
||
| private Predicate<String> validIssuer(Set<String> allowedTenantIds) { | ||
| return tid -> { | ||
| if (tid.startsWith(LOGIN_MICROSOFT_ONLINE_ISSUER) || tid.startsWith(STS_WINDOWS_ISSUER) || | ||
| tid.startsWith(STS_CHINA_CLOUD_API_ISSUER)) { | ||
| for (String allowedTenantId : allowedTenantIds) { | ||
| if (tid.contains(allowedTenantId)) { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| return false; | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * {@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,40 @@ | ||
| package com.azure.spring.aad.resource.validator; | ||
|
|
||
| import com.azure.spring.autoconfigure.aad.AADTokenClaim; | ||
| import java.util.Set; | ||
| 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; | ||
| import org.springframework.util.StringUtils; | ||
|
|
||
| public class AzureJwtTenantValidator implements OAuth2TokenValidator<Jwt> { | ||
|
|
||
| private final JwtClaimValidator<String> validator; | ||
|
|
||
| public AzureJwtTenantValidator(String tenantId, Set<String> allowedTenantids) { | ||
| Assert.notNull(tenantId, "tenantId cannot be null"); | ||
|
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. We need both to be non-null here?
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. Yes,It will be modified. |
||
| Assert.notNull(allowedTenantids, "allowedTenantids cannot be null"); | ||
| if (allowedTenantids.isEmpty()) { | ||
| if (StringUtils.isEmpty(tenantId)) { | ||
| this.validator = new JwtClaimValidator(AADTokenClaim.TID, tid -> true); | ||
| } else { | ||
| this.validator = new JwtClaimValidator(AADTokenClaim.TID, tenantId::equals); | ||
| } | ||
| } else { | ||
| this.validator = new JwtClaimValidator(AADTokenClaim.TID, allowedTenantids::contains); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| @Override | ||
| public OAuth2TokenValidatorResult validate(Jwt token) { | ||
| Assert.notNull(token, "token cannot be null"); | ||
| return this.validator.validate(token); | ||
| } | ||
|
|
||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need these properties?