org.springframework.security
spring-security-oauth2-jose
@@ -300,6 +306,7 @@
org.springframework.security:spring-security-config:[5.3.5.RELEASE]
org.springframework.security:spring-security-core:[5.3.5.RELEASE]
org.springframework.security:spring-security-oauth2-client:[5.3.5.RELEASE]
+ org.springframework.security:spring-security-oauth2-resource-server:[5.3.5.RELEASE]
org.springframework.security:spring-security-oauth2-jose:[5.3.5.RELEASE]
org.springframework.security:spring-security-web:[5.3.5.RELEASE]
org.apache.qpid:qpid-jms-client:[0.53.0]
diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/resource/server/AzureActiveDirectoryResourceServerConfiguration.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/resource/server/AzureActiveDirectoryResourceServerConfiguration.java
new file mode 100644
index 000000000000..8fd6086ef8bb
--- /dev/null
+++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/resource/server/AzureActiveDirectoryResourceServerConfiguration.java
@@ -0,0 +1,95 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.azure.spring.aad.resource.server;
+
+
+import com.azure.spring.aad.implementation.AuthorizationServerEndpoints;
+import com.azure.spring.aad.resource.server.validator.AzureJwtAudienceValidator;
+import com.azure.spring.aad.resource.server.validator.AzureJwtIssuerValidator;
+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.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;
+
+/**
+ *
+ * The configuration will not be activated if no {@link BearerTokenAuthenticationToken} class provided.
+ *
+ * By default, creating a JwtDecoder through JwkKeySetUri will be auto-configured.
+ */
+@Configuration(proxyBeanMethods = false)
+@EnableConfigurationProperties({AADAuthenticationProperties.class})
+@ConditionalOnClass(BearerTokenAuthenticationToken.class)
+public class AzureActiveDirectoryResourceServerConfiguration {
+
+ @Autowired
+ private AADAuthenticationProperties aadAuthenticationProperties;
+
+ /**
+ * Use JwkKeySetUri to create JwtDecoder
+ *
+ * @return JwtDecoder bean
+ */
+ @Bean
+ @ConditionalOnMissingBean(JwtDecoder.class)
+ public JwtDecoder jwtDecoderByJwkKeySetUri() {
+ if (StringUtils.isEmpty(aadAuthenticationProperties.getTenantId())) {
+ aadAuthenticationProperties.setTenantId("common");
+ }
+ AuthorizationServerEndpoints identityEndpoints = new AuthorizationServerEndpoints(
+ aadAuthenticationProperties.getAuthorizationServerUri());
+ NimbusJwtDecoder nimbusJwtDecoder = NimbusJwtDecoder
+ .withJwkSetUri(identityEndpoints.jwkSetEndpoint(aadAuthenticationProperties.getTenantId())).build();
+ List> validators = createDefaultValidator();
+ nimbusJwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(validators));
+ return nimbusJwtDecoder;
+ }
+
+ public List> createDefaultValidator() {
+ List> validators = new ArrayList<>();
+ if (!StringUtils.isEmpty(aadAuthenticationProperties.getAppIdUri())) {
+ List validAudiences = new ArrayList<>();
+ validAudiences.add(aadAuthenticationProperties.getAppIdUri());
+ validators.add(new AzureJwtAudienceValidator(validAudiences));
+ }
+ validators.add(new AzureJwtIssuerValidator());
+ 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());
+ }
+ }
+}
+
diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/resource/server/AzureJwtBearerTokenAuthenticationConverter.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/resource/server/AzureJwtBearerTokenAuthenticationConverter.java
new file mode 100644
index 000000000000..f5f7de17bc69
--- /dev/null
+++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/resource/server/AzureJwtBearerTokenAuthenticationConverter.java
@@ -0,0 +1,59 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.azure.spring.aad.resource.server;
+
+import java.util.Collection;
+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.jwt.Jwt;
+import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
+import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
+import org.springframework.util.Assert;
+
+/**
+ * A {@link Converter} that takes a {@link Jwt} and converts it into a {@link BearerTokenAuthentication}.
+ */
+public class AzureJwtBearerTokenAuthenticationConverter implements Converter {
+
+ private static final String DEFAULT_AUTHORITY_PREFIX = "SCOPE_";
+
+ private Converter> jwtGrantedConverter
+ = new JwtGrantedAuthoritiesConverter();
+
+ public AzureJwtBearerTokenAuthenticationConverter() {
+ }
+
+ public AzureJwtBearerTokenAuthenticationConverter(String authoritiesClaimName) {
+ this(authoritiesClaimName, DEFAULT_AUTHORITY_PREFIX);
+ }
+
+ public AzureJwtBearerTokenAuthenticationConverter(String authoritiesClaimName, String authorityPrefix) {
+ Assert.notNull(authoritiesClaimName, "authoritiesClaimName cannot be null");
+ Assert.notNull(authorityPrefix, "authorityPrefix cannot be null");
+ JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
+ jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(authoritiesClaimName);
+ jwtGrantedAuthoritiesConverter.setAuthorityPrefix(authorityPrefix);
+ this.jwtGrantedConverter = jwtGrantedAuthoritiesConverter;
+ }
+
+ protected Collection extractAuthorities(Jwt jwt) {
+ return this.jwtGrantedConverter.convert(jwt);
+ }
+
+ @Override
+ public AbstractAuthenticationToken convert(Jwt jwt) {
+ OAuth2AccessToken accessToken = new OAuth2AccessToken(
+ OAuth2AccessToken.TokenType.BEARER, jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt());
+ Collection authorities = extractAuthorities(jwt);
+ AzureOAuth2AuthenticatedPrincipal principal = new AzureOAuth2AuthenticatedPrincipal(
+ jwt.getHeaders(), jwt.getClaims(), authorities, jwt.getTokenValue());
+ return new BearerTokenAuthentication(principal, accessToken, authorities);
+ }
+
+ public void setJwtGrantedAuthoritiesConverter(
+ Converter> jwtGrantedAuthoritiesConverter) {
+ this.jwtGrantedConverter = jwtGrantedAuthoritiesConverter;
+ }
+}
diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/resource/server/AzureOAuth2AuthenticatedPrincipal.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/resource/server/AzureOAuth2AuthenticatedPrincipal.java
new file mode 100644
index 000000000000..e7f345a1a6dd
--- /dev/null
+++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/resource/server/AzureOAuth2AuthenticatedPrincipal.java
@@ -0,0 +1,101 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.azure.spring.aad.resource.server;
+
+import static org.springframework.security.core.authority.AuthorityUtils.NO_AUTHORITIES;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.JWTClaimsSet.Builder;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Map.Entry;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
+import org.springframework.util.Assert;
+
+/**
+ * entity class of AzureOAuth2AuthenticatedPrincipal
+ */
+public class AzureOAuth2AuthenticatedPrincipal implements OAuth2AuthenticatedPrincipal, Serializable {
+
+ private static final long serialVersionUID = -3625690847771476854L;
+
+ private final Collection authorities;
+
+ private final Map headers;
+
+ private final Map attributes;
+
+ private final String tokenValue;
+
+ private JWTClaimsSet jwtClaimsSet;
+
+ public AzureOAuth2AuthenticatedPrincipal(Map headers, Map attributes,
+ Collection authorities, String tokenValue) {
+ Assert.notEmpty(attributes, "attributes cannot be empty");
+ Assert.notEmpty(headers, "headers cannot be empty");
+ this.headers = headers;
+ this.tokenValue = tokenValue;
+ this.attributes = Collections.unmodifiableMap(attributes);
+ this.authorities = authorities == null ? NO_AUTHORITIES : Collections.unmodifiableCollection(authorities);
+ toJwtClaimsSet(attributes);
+ }
+
+ private void toJwtClaimsSet(Map attributes) {
+ JWTClaimsSet.Builder builder = new Builder();
+ for (Entry entry : attributes.entrySet()) {
+ builder.claim(entry.getKey(), entry.getValue());
+ }
+ this.jwtClaimsSet = builder.build();
+ }
+
+ @Override
+ public Map getAttributes() {
+ return attributes;
+ }
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ return authorities;
+ }
+
+ @Override
+ public String getName() {
+ return jwtClaimsSet == null ? null : (String) jwtClaimsSet.getClaim("name");
+ }
+
+ public String getTokenValue() {
+ return tokenValue;
+ }
+
+ public Map getHeaders() {
+ return headers;
+ }
+
+ public JWTClaimsSet getJwtClaimsSet() {
+ return jwtClaimsSet;
+ }
+
+ public String getIssuer() {
+ return jwtClaimsSet == null ? null : jwtClaimsSet.getIssuer();
+ }
+
+ public String getSubject() {
+ return jwtClaimsSet == null ? null : jwtClaimsSet.getSubject();
+ }
+
+ public Map getClaims() {
+ return jwtClaimsSet == null ? null : jwtClaimsSet.getClaims();
+ }
+
+ public Object getClaim(String name) {
+ return jwtClaimsSet == null ? null : jwtClaimsSet.getClaim(name);
+ }
+
+ public String getTenantId() {
+ return jwtClaimsSet == null ? null : (String) jwtClaimsSet.getClaim("tid");
+ }
+
+}
diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/resource/server/package-info.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/resource/server/package-info.java
new file mode 100644
index 000000000000..962b2f77134b
--- /dev/null
+++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/resource/server/package-info.java
@@ -0,0 +1,6 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+/**
+ * Package com.azure.spring.aad.resource.server
+ */
+package com.azure.spring.aad.resource.server;
diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/resource/server/validator/AzureJwtAudienceValidator.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/resource/server/validator/AzureJwtAudienceValidator.java
new file mode 100644
index 000000000000..5084a434352a
--- /dev/null
+++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/resource/server/validator/AzureJwtAudienceValidator.java
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.azure.spring.aad.resource.server.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;
+
+/**
+ * Validates the "aud" claim in a {@link Jwt}, that is matches a configured value
+ */
+public class AzureJwtAudienceValidator implements OAuth2TokenValidator {
+
+ private final JwtClaimValidator> validator;
+
+ /**
+ * Constructs a {@link AzureJwtAudienceValidator} using the provided parameters
+ *
+ * @param audiences - The audience that each {@link Jwt} should have.
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public AzureJwtAudienceValidator(List audiences) {
+ Assert.notNull(audiences, "audiences cannot be null");
+ this.validator = new JwtClaimValidator(AADTokenClaim.AUD, aud -> audiences.containsAll((List) aud));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public OAuth2TokenValidatorResult validate(Jwt token) {
+ Assert.notNull(token, "token cannot be null");
+ return this.validator.validate(token);
+ }
+
+
+}
diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/resource/server/validator/AzureJwtIssuerValidator.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/resource/server/validator/AzureJwtIssuerValidator.java
new file mode 100644
index 000000000000..e0acb05c22b3
--- /dev/null
+++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/resource/server/validator/AzureJwtIssuerValidator.java
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.azure.spring.aad.resource.server.validator;
+
+import com.azure.spring.autoconfigure.aad.AADTokenClaim;
+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 AzureJwtIssuerValidator implements OAuth2TokenValidator {
+
+ 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 validator;
+
+ /**
+ * Constructs a {@link AzureJwtIssuerValidator} using the provided parameters
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public AzureJwtIssuerValidator() {
+ this.validator = new JwtClaimValidator(AADTokenClaim.ISS, validIssuer());
+ }
+
+ private Predicate 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);
+ }
+
+}
diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/resource/server/validator/package-info.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/resource/server/validator/package-info.java
new file mode 100644
index 000000000000..99713f2a5f68
--- /dev/null
+++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/resource/server/validator/package-info.java
@@ -0,0 +1,6 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+/**
+ * Package com.azure.spring.aad.resource.server.validator
+ */
+package com.azure.spring.aad.resource.server.validator;
diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/aad/AADAuthenticationProperties.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/aad/AADAuthenticationProperties.java
index 13c7787d713f..099e5957bd65 100644
--- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/aad/AADAuthenticationProperties.java
+++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/aad/AADAuthenticationProperties.java
@@ -5,14 +5,6 @@
import com.azure.spring.aad.implementation.AuthorizationProperties;
import com.nimbusds.jose.jwk.source.RemoteJWKSet;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
-import org.springframework.validation.annotation.Validated;
-
-import javax.annotation.PostConstruct;
-import javax.validation.constraints.NotEmpty;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -21,6 +13,13 @@
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
+import javax.annotation.PostConstruct;
+import javax.validation.constraints.NotEmpty;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
+import org.springframework.validation.annotation.Validated;
/**
* Configuration properties for Azure Active Directory Authentication.
diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/aad/AADTokenClaim.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/aad/AADTokenClaim.java
index aa27ba1ec63a..057aa2da34f7 100644
--- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/aad/AADTokenClaim.java
+++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/aad/AADTokenClaim.java
@@ -11,4 +11,6 @@ public class AADTokenClaim {
public static final String NAME = "name";
public static final String TID = "tid";
public static final String ROLES = "roles";
+ public static final String ISS = "iss";
+ public static final String AUD = "aud";
}
diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/aad/UserPrincipal.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/aad/UserPrincipal.java
index fea3c0d87d12..e1a323352ff4 100644
--- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/aad/UserPrincipal.java
+++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/aad/UserPrincipal.java
@@ -105,6 +105,10 @@ public String getName() {
return jwtClaimsSet == null ? null : (String) jwtClaimsSet.getClaim("name");
}
+ public String getTenantId() {
+ return jwtClaimsSet == null ? null : (String) jwtClaimsSet.getClaim("tid");
+ }
+
public String getUserPrincipalName() {
return jwtClaimsSet == null ? null : (String) jwtClaimsSet.getClaim("preferred_username");
}
diff --git a/sdk/spring/azure-spring-boot/src/main/resources/META-INF/spring.factories b/sdk/spring/azure-spring-boot/src/main/resources/META-INF/spring.factories
index 53fc6120ab35..f85219dcf712 100644
--- a/sdk/spring/azure-spring-boot/src/main/resources/META-INF/spring.factories
+++ b/sdk/spring/azure-spring-boot/src/main/resources/META-INF/spring.factories
@@ -2,6 +2,8 @@ org.springframework.boot.env.EnvironmentPostProcessor=com.azure.spring.cloudfoun
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.azure.spring.autoconfigure.aad.AADAuthenticationFilterAutoConfiguration,\
com.azure.spring.autoconfigure.aad.AADOAuth2AutoConfiguration, \
+com.azure.spring.aad.implementation.AzureActiveDirectoryConfiguration,\
+com.azure.spring.aad.resource.server.AzureActiveDirectoryResourceServerConfiguration, \
com.azure.spring.autoconfigure.b2c.AADB2CAutoConfiguration,\
com.azure.spring.autoconfigure.cosmos.CosmosAutoConfiguration,\
com.azure.spring.autoconfigure.cosmos.CosmosHealthConfiguration,\
diff --git a/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/autoconfigure/aad/AzureActiveDirectoryResourceServerConfigurationTest.java b/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/autoconfigure/aad/AzureActiveDirectoryResourceServerConfigurationTest.java
new file mode 100644
index 000000000000..4119850edd1e
--- /dev/null
+++ b/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/autoconfigure/aad/AzureActiveDirectoryResourceServerConfigurationTest.java
@@ -0,0 +1,84 @@
+// 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 com.azure.spring.aad.resource.server.AzureActiveDirectoryResourceServerConfiguration;
+import com.azure.spring.aad.resource.server.AzureActiveDirectoryResourceServerConfiguration.DefaultAzureOAuth2ResourceServerWebSecurityConfigurerAdapter;
+import java.util.List;
+import org.junit.Test;
+import org.springframework.boot.test.context.FilteredClassLoader;
+import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+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.NimbusJwtDecoder;
+import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
+
+public class AzureActiveDirectoryResourceServerConfigurationTest {
+
+ private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
+ .withPropertyValues("azure.activedirectory.user-group.allowed-groups=User");
+
+ @Test
+ public void testNotExistBearerTokenAuthenticationToken() {
+ this.contextRunner
+ .withUserConfiguration(AzureActiveDirectoryResourceServerConfiguration.class)
+ .withClassLoader(new FilteredClassLoader(BearerTokenAuthenticationToken.class))
+ .run(context -> {
+ assertThat(context).doesNotHaveBean("jwtDecoderByJwkKeySetUri");
+ });
+ }
+
+ @Test
+ public void testCreateJwtDecoderByJwkKeySetUri() {
+ this.contextRunner
+ .withUserConfiguration(AzureActiveDirectoryResourceServerConfiguration.class)
+ .run(context -> {
+ final JwtDecoder jwtDecoder = context.getBean(JwtDecoder.class);
+ assertThat(jwtDecoder).isNotNull();
+ assertThat(jwtDecoder).isExactlyInstanceOf(NimbusJwtDecoder.class);
+ });
+ }
+
+ @Test
+ public void testNotAudienceDefaultValidator() {
+ this.contextRunner
+ .withUserConfiguration(AzureActiveDirectoryResourceServerConfiguration.class)
+ .run(context -> {
+ AzureActiveDirectoryResourceServerConfiguration bean = context
+ .getBean(AzureActiveDirectoryResourceServerConfiguration.class);
+ List> defaultValidator = bean.createDefaultValidator();
+ assertThat(defaultValidator).isNotNull();
+ assertThat(defaultValidator).hasSize(2);
+ });
+ }
+
+ @Test
+ public void testExistAudienceDefaultValidator() {
+ this.contextRunner
+ .withUserConfiguration(AzureActiveDirectoryResourceServerConfiguration.class)
+ .withPropertyValues("azure.activedirectory.app-id-uri=fake-app-id-uri")
+ .run(context -> {
+ AzureActiveDirectoryResourceServerConfiguration bean = context
+ .getBean(AzureActiveDirectoryResourceServerConfiguration.class);
+ List> defaultValidator = bean.createDefaultValidator();
+ assertThat(defaultValidator).isNotNull();
+ assertThat(defaultValidator).hasSize(3);
+ });
+ }
+
+ @Test
+ public void testCreateWebSecurityConfigurerAdapter() {
+ this.contextRunner
+ .withUserConfiguration(AzureActiveDirectoryResourceServerConfiguration.class)
+ .run(context -> {
+ WebSecurityConfigurerAdapter webSecurityConfigurerAdapter = context
+ .getBean(DefaultAzureOAuth2ResourceServerWebSecurityConfigurerAdapter.class);
+ assertThat(webSecurityConfigurerAdapter).isNotNull();
+ });
+ }
+
+}
diff --git a/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/autoconfigure/aad/AzureJwtAudienceValidatorTest.java b/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/autoconfigure/aad/AzureJwtAudienceValidatorTest.java
new file mode 100644
index 000000000000..f841faaf79df
--- /dev/null
+++ b/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/autoconfigure/aad/AzureJwtAudienceValidatorTest.java
@@ -0,0 +1,65 @@
+// 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 com.azure.spring.aad.resource.server.validator.AzureJwtAudienceValidator;
+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 AzureJwtAudienceValidatorTest {
+
+ final AADAuthenticationProperties aadAuthenticationProperties = mock(AADAuthenticationProperties.class);
+ final Jwt jwt = mock(Jwt.class);
+ final List audiences = new ArrayList<>();
+ final List 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());
+ AzureJwtAudienceValidator azureJwtAudienceValidator = new AzureJwtAudienceValidator(audiences);
+ OAuth2TokenValidatorResult result = azureJwtAudienceValidator.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());
+ AzureJwtAudienceValidator azureJwtAudienceValidator = new AzureJwtAudienceValidator(audiences);
+ OAuth2TokenValidatorResult result = azureJwtAudienceValidator.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());
+ AzureJwtAudienceValidator azureJwtAudienceValidator = new AzureJwtAudienceValidator(audiences);
+ OAuth2TokenValidatorResult result = azureJwtAudienceValidator.validate(jwt);
+
+ assertThat(result).isNotNull();
+ assertThat(result.getErrors()).isEmpty();
+ }
+}
diff --git a/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/autoconfigure/aad/AzureJwtBearerTokenAuthenticationConverterTest.java b/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/autoconfigure/aad/AzureJwtBearerTokenAuthenticationConverterTest.java
new file mode 100644
index 000000000000..cef153b3ac9b
--- /dev/null
+++ b/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/autoconfigure/aad/AzureJwtBearerTokenAuthenticationConverterTest.java
@@ -0,0 +1,79 @@
+// 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 com.azure.spring.aad.resource.server.AzureJwtBearerTokenAuthenticationConverter;
+import com.azure.spring.aad.resource.server.AzureOAuth2AuthenticatedPrincipal;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+public class AzureJwtBearerTokenAuthenticationConverterTest {
+
+ private Jwt jwt = mock(Jwt.class);
+ private Map claims = new HashMap<>();
+ private Map headers = new HashMap<>();
+
+ @Before
+ public void init() {
+ claims.put("iss", "fake-issuer");
+ claims.put("tid", "fake-tid");
+ headers.put("kid", "kg2LYs2T0CTjIfj4rt6JIynen38");
+ when(jwt.getClaim("scp")).thenReturn("Order.read Order.write");
+ when(jwt.getClaim("roles")).thenReturn("User.read User.write");
+ when(jwt.getTokenValue()).thenReturn("fake-token-value");
+ when(jwt.getIssuedAt()).thenReturn(Instant.now());
+ when(jwt.getHeaders()).thenReturn(headers);
+ when(jwt.getExpiresAt()).thenReturn(Instant.MAX);
+ when(jwt.getClaims()).thenReturn(claims);
+ when(jwt.containsClaim("scp")).thenReturn(true);
+ }
+
+ @Test
+ public void testCreateUserPrincipal() {
+ AzureJwtBearerTokenAuthenticationConverter azureJwtBearerTokenAuthenticationConverter
+ = new AzureJwtBearerTokenAuthenticationConverter();
+ AbstractAuthenticationToken authenticationToken = azureJwtBearerTokenAuthenticationConverter.convert(jwt);
+ assertThat(authenticationToken.getPrincipal()).isExactlyInstanceOf(AzureOAuth2AuthenticatedPrincipal.class);
+ AzureOAuth2AuthenticatedPrincipal principal = (AzureOAuth2AuthenticatedPrincipal) authenticationToken
+ .getPrincipal();
+ assertThat(principal.getClaims()).isNotEmpty();
+ assertThat(principal.getIssuer()).isEqualTo(claims.get("iss"));
+ assertThat(principal.getTenantId()).isEqualTo(claims.get("tid"));
+ }
+
+ @Test
+ public void testExtractDefaultScopeAuthorities() {
+ AzureJwtBearerTokenAuthenticationConverter azureJwtBearerTokenAuthenticationConverter
+ = new AzureJwtBearerTokenAuthenticationConverter();
+ AbstractAuthenticationToken authenticationToken = azureJwtBearerTokenAuthenticationConverter.convert(jwt);
+ assertThat(authenticationToken.getPrincipal()).isExactlyInstanceOf(AzureOAuth2AuthenticatedPrincipal.class);
+ AzureOAuth2AuthenticatedPrincipal principal = (AzureOAuth2AuthenticatedPrincipal) authenticationToken
+ .getPrincipal();
+ assertThat(principal.getAttributes()).isNotEmpty();
+ assertThat(principal.getAttributes()).hasSize(2);
+ }
+
+ @Test
+ public void testExtractCustomScopeAuthorities() {
+ when(jwt.containsClaim("roles")).thenReturn(true);
+ AzureJwtBearerTokenAuthenticationConverter azureJwtBearerTokenAuthenticationConverter
+ = new AzureJwtBearerTokenAuthenticationConverter("roles", "ROLE_");
+ AbstractAuthenticationToken authenticationToken = azureJwtBearerTokenAuthenticationConverter.convert(jwt);
+ assertThat(authenticationToken.getPrincipal()).isExactlyInstanceOf(AzureOAuth2AuthenticatedPrincipal.class);
+ AzureOAuth2AuthenticatedPrincipal principal = (AzureOAuth2AuthenticatedPrincipal) authenticationToken
+ .getPrincipal();
+ assertThat(principal.getAttributes()).isNotEmpty();
+ assertThat(principal.getAttributes()).hasSize(2);
+ }
+
+
+}
diff --git a/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/autoconfigure/aad/AzureJwtIssuerValidatorTest.java b/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/autoconfigure/aad/AzureJwtIssuerValidatorTest.java
new file mode 100644
index 000000000000..b1390dff0149
--- /dev/null
+++ b/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/autoconfigure/aad/AzureJwtIssuerValidatorTest.java
@@ -0,0 +1,41 @@
+// 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 com.azure.spring.aad.resource.server.validator.AzureJwtIssuerValidator;
+import org.junit.Test;
+import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+public class AzureJwtIssuerValidatorTest {
+
+ final AADAuthenticationProperties aadAuthenticationProperties = mock(AADAuthenticationProperties.class);
+ final Jwt jwt = mock(Jwt.class);
+
+ @Test
+ public void testIssuerSuccessVerify() {
+ when(aadAuthenticationProperties.getTenantId()).thenReturn("fake-tenant-id");
+ when(jwt.getClaim(AADTokenClaim.ISS)).thenReturn("https://sts.windows.net/fake-tenant-id/v2.0");
+
+ AzureJwtIssuerValidator azureJwtIssuerValidator = new AzureJwtIssuerValidator();
+ OAuth2TokenValidatorResult result = azureJwtIssuerValidator.validate(jwt);
+ assertThat(result).isNotNull();
+ assertThat(result.getErrors()).isEmpty();
+ }
+
+ @Test
+ public void testIssuerFailureVerify() {
+ when(aadAuthenticationProperties.getTenantId()).thenReturn("common");
+ when(jwt.getClaim(AADTokenClaim.ISS)).thenReturn("https://sts.failure.net/fake-tenant-id/v2.0");
+
+ AzureJwtIssuerValidator azureJwtIssuerValidator = new AzureJwtIssuerValidator();
+ OAuth2TokenValidatorResult result = azureJwtIssuerValidator.validate(jwt);
+ assertThat(result).isNotNull();
+ assertThat(result.getErrors()).isNotEmpty();
+ }
+
+}