diff --git a/sdk/spring/azure-spring-boot-starter-active-directory-b2c/CHANGELOG.md b/sdk/spring/azure-spring-boot-starter-active-directory-b2c/CHANGELOG.md index 00d0e89d831f3..f3d23d5d622b6 100644 --- a/sdk/spring/azure-spring-boot-starter-active-directory-b2c/CHANGELOG.md +++ b/sdk/spring/azure-spring-boot-starter-active-directory-b2c/CHANGELOG.md @@ -5,7 +5,10 @@ ## 3.6.0 (2021-06-23) ### Breaking Changes -- Deprecated `allowTelemetry` configuration item. +- Remove class `AADB2COAuth2AuthenticatedPrincipal`, use class `AADOAuth2AuthenticatedPrincipal` instead. + +### Deprecations +- Deprecate `allowTelemetry` configuration item. ## 3.5.0 (2021-05-24) ### New Features diff --git a/sdk/spring/azure-spring-boot-starter-active-directory-b2c/README.md b/sdk/spring/azure-spring-boot-starter-active-directory-b2c/README.md index 8a4a7a3a56e96..6620e0ad58acf 100644 --- a/sdk/spring/azure-spring-boot-starter-active-directory-b2c/README.md +++ b/sdk/spring/azure-spring-boot-starter-active-directory-b2c/README.md @@ -494,7 +494,24 @@ logging.level.org.hibernate=ERROR ``` For more information about setting logging in spring, please refer to the [official doc](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#boot-features-logging). - + +### Enable authority logging. + +Add the following logging settings: + +```properties +# logging settings for resource server scenario. +logging.level.com.azure.spring.aad.AADJwtGrantedAuthoritiesConverter=DEBUG +``` + +Then you will see logs like this: + +```text +... +DEBUG .a.s.a.AADJwtGrantedAuthoritiesConverter : User TestUser's authorities created from jwt token: [SCOPE_Test.Read, APPROLE_WebApi.ExampleScope]. +... +``` + ## Next steps ## Contributing diff --git a/sdk/spring/azure-spring-boot-starter-active-directory/CHANGELOG.md b/sdk/spring/azure-spring-boot-starter-active-directory/CHANGELOG.md index b3a49589f486f..7982904b16ea8 100644 --- a/sdk/spring/azure-spring-boot-starter-active-directory/CHANGELOG.md +++ b/sdk/spring/azure-spring-boot-starter-active-directory/CHANGELOG.md @@ -5,8 +5,10 @@ ## 3.6.0 (2021-06-23) ### Breaking Changes + +### Deprecations - Deprecate aad.group.enable-full-list, use aad.group.allowed-group-ids=all instead. -- Deprecated `allowTelemetry` configuration item. +- Deprecate `allowTelemetry` configuration item. ### New Features - Support domain_hint in aad-starter.([#21517](https://github.com/Azure/azure-sdk-for-java/issues/21517)) diff --git a/sdk/spring/azure-spring-boot-starter-active-directory/README.md b/sdk/spring/azure-spring-boot-starter-active-directory/README.md index ecd28da47b7c6..fddb6b7438398 100644 --- a/sdk/spring/azure-spring-boot-starter-active-directory/README.md +++ b/sdk/spring/azure-spring-boot-starter-active-directory/README.md @@ -650,6 +650,36 @@ logging.level.org.hibernate=ERROR For more information about setting logging in spring, please refer to the [official doc]. +### Enable authority logging. + +Add the following logging settings: + +```properties +# logging settings for web application scenario. +logging.level.com.azure.spring.aad.webapp.AADOAuth2UserService=DEBUG + +# logging settings for resource server scenario. +logging.level.com.azure.spring.aad.AADJwtGrantedAuthoritiesConverter=DEBUG +``` + +Then you will see log like this in web application: + +```text +... +DEBUG c.a.s.aad.webapp.AADOAuth2UserService : User TestUser's authorities extracted by id token and access token: [ROLE_group1, ROLE_group2]. +... +DEBUG c.a.s.aad.webapp.AADOAuth2UserService : User TestUser's authorities saved from session: [ROLE_group1, ROLE_group2]. +... +``` + +Or log like this in resource server: + +```text +... +DEBUG .a.s.a.AADJwtGrantedAuthoritiesConverter : User TestUser's authorities created from jwt token: [SCOPE_Test.Read, APPROLE_WebApi.ExampleScope]. +... +``` + ## Next steps ## Contributing diff --git a/sdk/spring/azure-spring-boot-starter-cosmos/CHANGELOG.md b/sdk/spring/azure-spring-boot-starter-cosmos/CHANGELOG.md index 64c15b18da692..50aa0079cd6a8 100644 --- a/sdk/spring/azure-spring-boot-starter-cosmos/CHANGELOG.md +++ b/sdk/spring/azure-spring-boot-starter-cosmos/CHANGELOG.md @@ -5,7 +5,9 @@ ## 3.6.0 (2021-06-23) ### Breaking Changes -- Deprecated `allowTelemetry` configuration item. + +### Deprecations +- Deprecate `allowTelemetry` configuration item. ## 3.5.0 (2021-05-24) ### New Features diff --git a/sdk/spring/azure-spring-boot-starter-keyvault-secrets/CHANGELOG.md b/sdk/spring/azure-spring-boot-starter-keyvault-secrets/CHANGELOG.md index afde8ff17f170..d26e990046df1 100644 --- a/sdk/spring/azure-spring-boot-starter-keyvault-secrets/CHANGELOG.md +++ b/sdk/spring/azure-spring-boot-starter-keyvault-secrets/CHANGELOG.md @@ -5,7 +5,9 @@ ## 3.6.0 (2021-06-23) ### Breaking Changes -- Deprecated `allowTelemetry` configuration item. + +### Deprecations +- Deprecate `allowTelemetry` configuration item. ## 3.5.0 (2021-05-24) ### New Features diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/AADJwtGrantedAuthoritiesConverter.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/AADJwtGrantedAuthoritiesConverter.java new file mode 100644 index 0000000000000..09486ff0e064b --- /dev/null +++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/AADJwtGrantedAuthoritiesConverter.java @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.spring.aad; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.jwt.Jwt; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +/** + * Extracts the {@link GrantedAuthority}s from scope attributes typically found in a {@link Jwt}. + */ +public class AADJwtGrantedAuthoritiesConverter implements Converter> { + + private static final Logger LOGGER = LoggerFactory.getLogger(AADJwtGrantedAuthoritiesConverter.class); + public static final Map DEFAULT_CLAIM_TO_AUTHORITY_PREFIX_MAP; + public static final String DEFAULT_AUTHORITY_CLAIM_NAME = "scp"; + public static final String DEFAULT_AUTHORITY_PREFIX = "SCOPE_"; + + static { + Map claimAuthorityMap = new HashMap<>(); + claimAuthorityMap.put(DEFAULT_AUTHORITY_CLAIM_NAME, DEFAULT_AUTHORITY_PREFIX); + claimAuthorityMap.put("roles", "APPROLE_"); + DEFAULT_CLAIM_TO_AUTHORITY_PREFIX_MAP = Collections.unmodifiableMap(claimAuthorityMap); + } + + private final Map claimToAuthorityPrefixMap; + + public AADJwtGrantedAuthoritiesConverter() { + claimToAuthorityPrefixMap = DEFAULT_CLAIM_TO_AUTHORITY_PREFIX_MAP; + } + + public AADJwtGrantedAuthoritiesConverter(Map claimToAuthorityPrefixMap) { + this.claimToAuthorityPrefixMap = claimToAuthorityPrefixMap; + } + + @Override + public Collection convert(Jwt jwt) { + Collection grantedAuthorities = new ArrayList<>(); + claimToAuthorityPrefixMap.forEach((authoritiesClaimName, authorityPrefix) -> { + Optional.of(authoritiesClaimName) + .map(jwt::getClaim) + .map(this::getClaimValueAsCollection) + .map(Collection::stream) + .orElseGet(Stream::empty) + .map(authority -> authorityPrefix + authority) + .map(SimpleGrantedAuthority::new) + .forEach(grantedAuthorities::add); + + }); + LOGGER.debug("User {}'s authorities created from jwt token: {}.", jwt.getSubject(), grantedAuthorities); + return grantedAuthorities; + } + + private Collection getClaimValueAsCollection(Object claimValue) { + if (claimValue instanceof String) { + return Arrays.asList(((String) claimValue).split(" ")); + } else if (claimValue instanceof Collection) { + return (Collection) claimValue; + } else { + return Collections.emptyList(); + } + } +} diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapi/AADOAuth2AuthenticatedPrincipal.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/AADOAuth2AuthenticatedPrincipal.java similarity index 93% rename from sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapi/AADOAuth2AuthenticatedPrincipal.java rename to sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/AADOAuth2AuthenticatedPrincipal.java index ada5e1ab65d7b..0a7fbe3e54cc9 100644 --- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapi/AADOAuth2AuthenticatedPrincipal.java +++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/AADOAuth2AuthenticatedPrincipal.java @@ -1,6 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.spring.aad.webapi; +package com.azure.spring.aad; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.JWTClaimsSet.Builder; @@ -17,7 +17,7 @@ import static org.springframework.security.core.authority.AuthorityUtils.NO_AUTHORITIES; /** - * entity class of AADOAuth2AuthenticatedPrincipal + * Entity class of AADOAuth2AuthenticatedPrincipal */ public class AADOAuth2AuthenticatedPrincipal implements OAuth2AuthenticatedPrincipal, Serializable { @@ -33,22 +33,22 @@ public class AADOAuth2AuthenticatedPrincipal implements OAuth2AuthenticatedPrinc private final String tokenValue; - private final String name; - private JWTClaimsSet jwtClaimsSet; + private final String name; + public AADOAuth2AuthenticatedPrincipal(Map headers, Map attributes, Collection authorities, String tokenValue, - String principalClaimName) { + String name) { 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); - this.name = (String) this.attributes.get(principalClaimName); + this.name = name; toJwtClaimsSet(attributes); } diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/AbstractJwtBearerTokenAuthenticationConverter.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/AbstractJwtBearerTokenAuthenticationConverter.java new file mode 100644 index 0000000000000..06f7591278d1a --- /dev/null +++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/AbstractJwtBearerTokenAuthenticationConverter.java @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.spring.aad; + +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.OAuth2AuthenticatedPrincipal; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication; +import org.springframework.util.Assert; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * An abstract {@link Converter} that takes a {@link Jwt} and converts it into a {@link BearerTokenAuthentication}. + */ +public abstract class AbstractJwtBearerTokenAuthenticationConverter implements Converter { + + public static final String DEFAULT_PRINCIPAL_CLAIM_NAME = "sub"; + protected Converter> converter; + protected String principalClaimName; + + public AbstractJwtBearerTokenAuthenticationConverter(String principalClaimName, + Map claimToAuthorityPrefixMap) { + Assert.notNull(claimToAuthorityPrefixMap, "claimToAuthorityPrefixMap cannot be null"); + this.principalClaimName = principalClaimName; + this.converter = new AADJwtGrantedAuthoritiesConverter(claimToAuthorityPrefixMap); + } + + @Override + public AbstractAuthenticationToken convert(Jwt jwt) { + OAuth2AccessToken accessToken = new OAuth2AccessToken( + OAuth2AccessToken.TokenType.BEARER, jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt()); + Collection authorities = converter.convert(jwt); + OAuth2AuthenticatedPrincipal principal = getAuthenticatedPrincipal( + jwt.getHeaders(), jwt.getClaims(), authorities, jwt.getTokenValue()); + return new BearerTokenAuthentication(principal, accessToken, authorities); + } + + protected static Map buildClaimToAuthorityPrefixMap(String authoritiesClaimName, + String authorityPrefix) { + Assert.notNull(authoritiesClaimName, "authoritiesClaimName cannot be null"); + Assert.notNull(authorityPrefix, "authorityPrefix cannot be null"); + Map claimToAuthorityPrefixMap = new HashMap<>(); + claimToAuthorityPrefixMap.put(authoritiesClaimName, authorityPrefix); + return claimToAuthorityPrefixMap; + } + + /** + * Construct an instance of OAuth2AuthenticatedPrincipal interface. + * + * @param headers Jwt header map + * @param claims Jwt claims map + * @param authorities Jwt authorities collection + * @param tokenValue Jwt token value + * @return an OAuth2AuthenticatedPrincipal instance. + */ + protected abstract OAuth2AuthenticatedPrincipal getAuthenticatedPrincipal(Map headers, + Map claims, + Collection authorities, + String tokenValue); + + public void setConverter( + Converter> converter) { + this.converter = converter; + } + + public void setPrincipalClaimName(String principalClaimName) { + Assert.hasText(principalClaimName, "principalClaimName cannot be empty"); + this.principalClaimName = principalClaimName; + } +} diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/common/AbstractOAuth2AuthorizationCodeGrantRequestEntityConverter.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/AbstractOAuth2AuthorizationCodeGrantRequestEntityConverter.java similarity index 98% rename from sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/common/AbstractOAuth2AuthorizationCodeGrantRequestEntityConverter.java rename to sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/AbstractOAuth2AuthorizationCodeGrantRequestEntityConverter.java index 49c6b450a6b6d..d14d284bd7227 100644 --- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/common/AbstractOAuth2AuthorizationCodeGrantRequestEntityConverter.java +++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/AbstractOAuth2AuthorizationCodeGrantRequestEntityConverter.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.azure.spring.common; +package com.azure.spring.aad; import com.azure.spring.utils.ApplicationId; import org.springframework.http.HttpEntity; diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapi/AADJwtBearerTokenAuthenticationConverter.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapi/AADJwtBearerTokenAuthenticationConverter.java index 4cac3fc2a9314..238e29f3d04aa 100644 --- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapi/AADJwtBearerTokenAuthenticationConverter.java +++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapi/AADJwtBearerTokenAuthenticationConverter.java @@ -2,74 +2,70 @@ // Licensed under the MIT License. package com.azure.spring.aad.webapi; +import com.azure.spring.aad.AADOAuth2AuthenticatedPrincipal; +import com.azure.spring.aad.AbstractJwtBearerTokenAuthenticationConverter; 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.OAuth2AuthenticatedPrincipal; 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; import java.util.Collection; +import java.util.Map; +import java.util.Optional; + +import static com.azure.spring.aad.AADJwtGrantedAuthoritiesConverter.DEFAULT_AUTHORITY_PREFIX; +import static com.azure.spring.aad.AADJwtGrantedAuthoritiesConverter.DEFAULT_CLAIM_TO_AUTHORITY_PREFIX_MAP; /** * A {@link Converter} that takes a {@link Jwt} and converts it into a {@link BearerTokenAuthentication}. */ -public class AADJwtBearerTokenAuthenticationConverter implements Converter { - - private static final String DEFAULT_AUTHORITY_PREFIX = "SCOPE_"; - private static final String DEFAULT_PRINCIPAL_CLAIM_NAME = "sub"; - - private Converter> jwtGrantedAuthoritiesConverter; - private String principalClaimName = DEFAULT_PRINCIPAL_CLAIM_NAME; +public class AADJwtBearerTokenAuthenticationConverter extends AbstractJwtBearerTokenAuthenticationConverter { /** - * Use AADJwtGrantedAuthoritiesConverter, it can resolve the access token of scp and roles. + * Construct AADJwtBearerTokenAuthenticationConverter by DEFAULT_PRINCIPAL_CLAIM_NAME and DEFAULT_CLAIM_TO_AUTHORITY_PREFIX_MAP. */ public AADJwtBearerTokenAuthenticationConverter() { - this.jwtGrantedAuthoritiesConverter = new AADJwtGrantedAuthoritiesConverter(); + this(DEFAULT_PRINCIPAL_CLAIM_NAME, DEFAULT_CLAIM_TO_AUTHORITY_PREFIX_MAP); } /** - * Using spring security provides JwtGrantedAuthoritiesConverter, it can resolve the access token of scp or roles. - * - * @param authoritiesClaimName authorities claim name + * Construct AADJwtBearerTokenAuthenticationConverter with the authority claim. + * @param authoritiesClaimName authority claim name */ public AADJwtBearerTokenAuthenticationConverter(String authoritiesClaimName) { this(authoritiesClaimName, DEFAULT_AUTHORITY_PREFIX); } - public AADJwtBearerTokenAuthenticationConverter(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.jwtGrantedAuthoritiesConverter = jwtGrantedAuthoritiesConverter; + /** + * Construct AADJwtBearerTokenAuthenticationConverter with the authority claim name and prefix. + * @param authoritiesClaimName authority claim name + * @param authorityPrefix the prefix name of the authority + */ + public AADJwtBearerTokenAuthenticationConverter(String authoritiesClaimName, + String authorityPrefix) { + this(null, buildClaimToAuthorityPrefixMap(authoritiesClaimName, authorityPrefix)); } - protected Collection extractAuthorities(Jwt jwt) { - return this.jwtGrantedAuthoritiesConverter.convert(jwt); + /** + * Using spring security provides JwtGrantedAuthoritiesConverter, it can resolve the access token of scp or roles. + * + * @param principalClaimName authorities claim name + * @param claimToAuthorityPrefixMap the authority name and prefix map + */ + public AADJwtBearerTokenAuthenticationConverter(String principalClaimName, + Map claimToAuthorityPrefixMap) { + super(principalClaimName, claimToAuthorityPrefixMap); } @Override - public AbstractAuthenticationToken convert(Jwt jwt) { - OAuth2AccessToken accessToken = new OAuth2AccessToken( - OAuth2AccessToken.TokenType.BEARER, jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt()); - Collection authorities = extractAuthorities(jwt); - AADOAuth2AuthenticatedPrincipal principal = new AADOAuth2AuthenticatedPrincipal( - jwt.getHeaders(), jwt.getClaims(), authorities, jwt.getTokenValue(), principalClaimName); - return new BearerTokenAuthentication(principal, accessToken, authorities); - } - - public void setJwtGrantedAuthoritiesConverter( - Converter> jwtGrantedAuthoritiesConverter) { - this.jwtGrantedAuthoritiesConverter = jwtGrantedAuthoritiesConverter; - } - - public void setPrincipalClaimName(String principalClaimName) { - Assert.hasText(principalClaimName, "principalClaimName cannot be empty"); - this.principalClaimName = principalClaimName; + protected OAuth2AuthenticatedPrincipal getAuthenticatedPrincipal(Map headers, + Map claims, + Collection authorities, + String tokenValue) { + String name = Optional.ofNullable(principalClaimName) + .map(n -> (String) claims.get(n)) + .orElseGet(() -> (String) claims.get("sub")); + return new AADOAuth2AuthenticatedPrincipal(headers, claims, authorities, tokenValue, name); } } diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapi/AADJwtGrantedAuthoritiesConverter.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapi/AADJwtGrantedAuthoritiesConverter.java deleted file mode 100644 index 8871e88e6360f..0000000000000 --- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapi/AADJwtGrantedAuthoritiesConverter.java +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -package com.azure.spring.aad.webapi; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.util.StringUtils; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.stream.Collectors; - -/** - * Extracts the {@link GrantedAuthority}s from scope attributes typically found in a {@link Jwt}. - */ -public class AADJwtGrantedAuthoritiesConverter implements Converter> { - - private static final String DEFAULT_SCP_AUTHORITY_PREFIX = "SCOPE_"; - - private static final String DEFAULT_ROLES_AUTHORITY_PREFIX = "APPROLE_"; - - private static final Collection WELL_KNOWN_AUTHORITIES_CLAIM_NAMES = Arrays.asList("scp", "roles"); - - @Override - public Collection convert(Jwt jwt) { - Collection grantedAuthorities = new ArrayList<>(); - for (String authority : getAuthorities(jwt)) { - grantedAuthorities.add(new SimpleGrantedAuthority(authority)); - } - return grantedAuthorities; - } - - private Collection getAuthorities(Jwt jwt) { - Collection authoritiesList = new ArrayList(); - for (String claimName : WELL_KNOWN_AUTHORITIES_CLAIM_NAMES) { - if (jwt.containsClaim(claimName)) { - if (jwt.getClaim(claimName) instanceof String) { - if (StringUtils.hasText(jwt.getClaim(claimName))) { - authoritiesList.addAll(Arrays.asList(((String) jwt.getClaim(claimName)).split(" ")) - .stream() - .map(s -> DEFAULT_SCP_AUTHORITY_PREFIX + s) - .collect(Collectors.toList())); - } - } else if (jwt.getClaim(claimName) instanceof Collection) { - authoritiesList.addAll(((Collection) jwt.getClaim(claimName)) - .stream() - .filter(s -> StringUtils.hasText((String) s)) - .map(s -> DEFAULT_ROLES_AUTHORITY_PREFIX + s) - .collect(Collectors.toList())); - } - } - } - return authoritiesList; - } -} diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADOAuth2AuthorizationCodeGrantRequestEntityConverter.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADOAuth2AuthorizationCodeGrantRequestEntityConverter.java index 861940d43b4a7..0d773d9b99d58 100644 --- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADOAuth2AuthorizationCodeGrantRequestEntityConverter.java +++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADOAuth2AuthorizationCodeGrantRequestEntityConverter.java @@ -3,7 +3,7 @@ package com.azure.spring.aad.webapp; -import com.azure.spring.common.AbstractOAuth2AuthorizationCodeGrantRequestEntityConverter; +import com.azure.spring.aad.AbstractOAuth2AuthorizationCodeGrantRequestEntityConverter; import com.azure.spring.utils.ApplicationId; import org.springframework.http.RequestEntity; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADOAuth2UserService.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADOAuth2UserService.java index ea180150b559d..4ea7d353b532b 100644 --- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADOAuth2UserService.java +++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADOAuth2UserService.java @@ -3,6 +3,8 @@ package com.azure.spring.aad.webapp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.azure.spring.autoconfigure.aad.AADAuthenticationProperties; import com.azure.spring.autoconfigure.aad.AADTokenClaim; import org.springframework.security.core.Authentication; @@ -44,6 +46,8 @@ */ public class AADOAuth2UserService implements OAuth2UserService { + private static final Logger LOGGER = LoggerFactory.getLogger(AADOAuth2UserService.class); + private final OidcUserService oidcUserService; private final List allowedGroupNames; private final Set allowedGroupIds; @@ -84,6 +88,7 @@ public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2Authenticatio HttpSession session = attr.getRequest().getSession(true); if (authentication != null) { + LOGGER.debug("User {}'s authorities saved from session: {}.", authentication.getName(), authentication.getAuthorities()); return (DefaultOidcUser) session.getAttribute(DEFAULT_OIDC_USER); } @@ -104,6 +109,7 @@ public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2Authenticatio .map(ClientRegistration.ProviderDetails.UserInfoEndpoint::getUserNameAttributeName) .filter(StringUtils::hasText) .orElse(AADTokenClaim.NAME); + LOGGER.debug("User {}'s authorities extracted by id token and access token: {}.", oidcUser.getClaim(nameAttributeKey), authorities); // Create a copy of oidcUser but use the mappedAuthorities instead DefaultOidcUser defaultOidcUser = new DefaultOidcUser(authorities, idToken, nameAttributeKey); diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/b2c/AADB2CAuthorizationRequestResolver.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/b2c/AADB2CAuthorizationRequestResolver.java index d0db7cebc7950..95a39f1c6bbbe 100644 --- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/b2c/AADB2CAuthorizationRequestResolver.java +++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/b2c/AADB2CAuthorizationRequestResolver.java @@ -97,7 +97,7 @@ private OAuth2AuthorizationRequest getB2CAuthorizationRequest(@Nullable OAuth2Au private String getRegistrationId(HttpServletRequest request) { if (REQUEST_MATCHER.matches(request)) { - return REQUEST_MATCHER.extractUriTemplateVariables(request).get(REGISTRATION_ID_NAME); + return REQUEST_MATCHER.matcher(request).getVariables().get(REGISTRATION_ID_NAME); } return null; diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/b2c/AADB2CJwtBearerTokenAuthenticationConverter.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/b2c/AADB2CJwtBearerTokenAuthenticationConverter.java index 55576e60f4840..043321b0bba8e 100644 --- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/b2c/AADB2CJwtBearerTokenAuthenticationConverter.java +++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/b2c/AADB2CJwtBearerTokenAuthenticationConverter.java @@ -2,79 +2,64 @@ // Licensed under the MIT License. package com.azure.spring.autoconfigure.b2c; +import com.azure.spring.aad.AADJwtGrantedAuthoritiesConverter; +import com.azure.spring.aad.AADOAuth2AuthenticatedPrincipal; +import com.azure.spring.aad.AbstractJwtBearerTokenAuthenticationConverter; 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.OAuth2AuthenticatedPrincipal; 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; import java.util.Collection; +import java.util.Map; +import java.util.Optional; + +import static com.azure.spring.aad.AADJwtGrantedAuthoritiesConverter.DEFAULT_AUTHORITY_PREFIX; +import static com.azure.spring.aad.AADJwtGrantedAuthoritiesConverter.DEFAULT_CLAIM_TO_AUTHORITY_PREFIX_MAP; /** * A {@link Converter} that takes a {@link Jwt} and converts it into a {@link BearerTokenAuthentication}. */ -public class AADB2CJwtBearerTokenAuthenticationConverter implements Converter { - - private static final String DEFAULT_AUTHORITY_PREFIX = "SCOPE_"; - - private Converter> converter; - - private String principalClaimName; +public class AADB2CJwtBearerTokenAuthenticationConverter extends AbstractJwtBearerTokenAuthenticationConverter { /** - * Use AADB2CJwtGrantedAuthoritiesConverter, it can resolve the access token of scp and roles. + * Use {@link AADJwtGrantedAuthoritiesConverter}, it can resolve the access token of scp and roles. */ public AADB2CJwtBearerTokenAuthenticationConverter() { - this.converter = new AADB2CJwtGrantedAuthoritiesConverter(); + this(null, DEFAULT_CLAIM_TO_AUTHORITY_PREFIX_MAP); } /** - * Use JwtGrantedAuthoritiesConverter, it can resolve the access token of scp or roles. - * @param authoritiesClaimName authorities claim name + * Construct AADB2CJwtBearerTokenAuthenticationConverter with the authority claim. + * @param authoritiesClaimName authority claim name */ public AADB2CJwtBearerTokenAuthenticationConverter(String authoritiesClaimName) { this(authoritiesClaimName, DEFAULT_AUTHORITY_PREFIX); } + /** + * Construct AADB2CJwtBearerTokenAuthenticationConverter with the authority claim name and prefix. + * @param authoritiesClaimName authority claim name + * @param authorityPrefix the prefix name of the authority + */ public AADB2CJwtBearerTokenAuthenticationConverter(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.converter = jwtGrantedAuthoritiesConverter; + this(null, buildClaimToAuthorityPrefixMap(authoritiesClaimName, authorityPrefix)); } - protected Collection extractAuthorities(Jwt jwt) { - return this.converter.convert(jwt); + public AADB2CJwtBearerTokenAuthenticationConverter(String principalClaimName, + Map claimToAuthorityPrefixMap) { + super(principalClaimName, claimToAuthorityPrefixMap); } @Override - public AbstractAuthenticationToken convert(Jwt jwt) { - OAuth2AccessToken accessToken = new OAuth2AccessToken( - OAuth2AccessToken.TokenType.BEARER, jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt()); - Collection authorities = extractAuthorities(jwt); - if (this.principalClaimName == null) { - AADB2COAuth2AuthenticatedPrincipal principal = new AADB2COAuth2AuthenticatedPrincipal( - jwt.getHeaders(), jwt.getClaims(), authorities, jwt.getTokenValue()); - return new BearerTokenAuthentication(principal, accessToken, authorities); - } - String name = jwt.getClaim(this.principalClaimName); - AADB2COAuth2AuthenticatedPrincipal principal = new AADB2COAuth2AuthenticatedPrincipal(jwt.getHeaders(), - jwt.getClaims(), authorities, jwt.getTokenValue(), name); - return new BearerTokenAuthentication(principal, accessToken, authorities); - } - - public void setJwtGrantedAuthoritiesConverter( - Converter> jwtGrantedAuthoritiesConverter) { - this.converter = jwtGrantedAuthoritiesConverter; - } - - public void setPrincipalClaimName(String principalClaimName) { - Assert.hasText(principalClaimName, "principalClaimName cannot be empty"); - this.principalClaimName = principalClaimName; + protected OAuth2AuthenticatedPrincipal getAuthenticatedPrincipal(Map headers, + Map claims, + Collection authorities, + String tokenValue) { + String name = Optional.ofNullable(principalClaimName) + .map(n -> (String) claims.get(n)) + .orElseGet(() -> (String) claims.get("sub")); + return new AADOAuth2AuthenticatedPrincipal(headers, claims, authorities, tokenValue, name); } } diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/b2c/AADB2CJwtGrantedAuthoritiesConverter.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/b2c/AADB2CJwtGrantedAuthoritiesConverter.java deleted file mode 100644 index 9d5973b181daa..0000000000000 --- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/b2c/AADB2CJwtGrantedAuthoritiesConverter.java +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -package com.azure.spring.autoconfigure.b2c; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.util.StringUtils; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.stream.Collectors; - -/** - * Extracts the {@link GrantedAuthority}s from scope attributes typically found in a {@link Jwt}. - */ -public class AADB2CJwtGrantedAuthoritiesConverter implements Converter> { - - private static final String DEFAULT_SCP_AUTHORITY_PREFIX = "SCOPE_"; - - private static final String DEFAULT_ROLES_AUTHORITY_PREFIX = "APPROLE_"; - - private static final Collection WELL_KNOWN_AUTHORITIES_CLAIM_NAMES = Arrays.asList("scp", "roles"); - - @Override - public Collection convert(Jwt jwt) { - Collection grantedAuthorities = new ArrayList<>(); - for (String authority : getAuthorities(jwt)) { - grantedAuthorities.add(new SimpleGrantedAuthority(authority)); - } - return grantedAuthorities; - } - - private Collection getAuthorities(Jwt jwt) { - Collection authoritiesList = new ArrayList(); - for (String claimName : WELL_KNOWN_AUTHORITIES_CLAIM_NAMES) { - if (jwt.containsClaim(claimName)) { - if (jwt.getClaim(claimName) instanceof String) { - if (StringUtils.hasText(jwt.getClaim(claimName))) { - authoritiesList.addAll(Arrays.asList(((String) jwt.getClaim(claimName)).split(" ")) - .stream() - .map(s -> DEFAULT_SCP_AUTHORITY_PREFIX + s) - .collect(Collectors.toList())); - } - } else if (jwt.getClaim(claimName) instanceof Collection) { - authoritiesList.addAll(((Collection) jwt.getClaim(claimName)) - .stream() - .filter(s -> StringUtils.hasText((String) s)) - .map(s -> DEFAULT_ROLES_AUTHORITY_PREFIX + s) - .collect(Collectors.toList())); - } - } - } - return authoritiesList; - } -} diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/b2c/AADB2COAuth2AuthenticatedPrincipal.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/b2c/AADB2COAuth2AuthenticatedPrincipal.java deleted file mode 100644 index 0ee0e41766273..0000000000000 --- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/b2c/AADB2COAuth2AuthenticatedPrincipal.java +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -package com.azure.spring.autoconfigure.b2c; - -import com.nimbusds.jwt.JWTClaimsSet; -import com.nimbusds.jwt.JWTClaimsSet.Builder; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; -import org.springframework.util.Assert; - -import java.io.Serializable; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.Map.Entry; - -import static org.springframework.security.core.authority.AuthorityUtils.NO_AUTHORITIES; - -/** - * entity class of AADB2COAuth2AuthenticatedPrincipal - */ -public class AADB2COAuth2AuthenticatedPrincipal 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; - - private final String name; - - public AADB2COAuth2AuthenticatedPrincipal(Map headers, - Map attributes, - Collection authorities, - String tokenValue) { - this(headers, attributes, authorities, tokenValue, null); - } - - public AADB2COAuth2AuthenticatedPrincipal(Map headers, - Map attributes, - Collection authorities, - String tokenValue, String name) { - 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); - this.name = (name != null) ? name : (String) this.attributes.get("sub"); - toJwtClaimsSet(attributes); - } - - private void toJwtClaimsSet(Map attributes) { - 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 getAuthorities() { - return authorities; - } - - @Override - public String getName() { - return this.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/autoconfigure/b2c/AADB2COAuth2AuthorizationCodeGrantRequestEntityConverter.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/b2c/AADB2COAuth2AuthorizationCodeGrantRequestEntityConverter.java index 2d205028d8e9a..511593549507e 100644 --- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/b2c/AADB2COAuth2AuthorizationCodeGrantRequestEntityConverter.java +++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/b2c/AADB2COAuth2AuthorizationCodeGrantRequestEntityConverter.java @@ -3,7 +3,7 @@ package com.azure.spring.autoconfigure.b2c; -import com.azure.spring.common.AbstractOAuth2AuthorizationCodeGrantRequestEntityConverter; +import com.azure.spring.aad.AbstractOAuth2AuthorizationCodeGrantRequestEntityConverter; import com.azure.spring.utils.ApplicationId; import org.springframework.http.RequestEntity; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/common/package-info.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/common/package-info.java deleted file mode 100644 index 9e93a0667c0d5..0000000000000 --- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/common/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -/** - * Package com.azure.spring.common - */ -package com.azure.spring.common; diff --git a/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/aad/webapi/AADJwtBearerTokenAuthenticationConverterTest.java b/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/aad/webapi/AADJwtBearerTokenAuthenticationConverterTest.java index 2d38b16f25838..83456387131fd 100644 --- a/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/aad/webapi/AADJwtBearerTokenAuthenticationConverterTest.java +++ b/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/aad/webapi/AADJwtBearerTokenAuthenticationConverterTest.java @@ -2,10 +2,14 @@ // Licensed under the MIT License. package com.azure.spring.aad.webapi; +import com.azure.spring.aad.AADOAuth2AuthenticatedPrincipal; import net.minidev.json.JSONArray; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.jwt.Jwt; import java.time.Instant; @@ -16,6 +20,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class AADJwtBearerTokenAuthenticationConverterTest { private Jwt jwt = mock(Jwt.class); @@ -23,7 +28,7 @@ public class AADJwtBearerTokenAuthenticationConverterTest { private Map headers = new HashMap<>(); private JSONArray jsonArray = new JSONArray().appendElement("User.read").appendElement("User.write"); - @BeforeEach + @BeforeAll public void init() { claims.put("iss", "fake-issuer"); claims.put("tid", "fake-tid"); @@ -51,8 +56,6 @@ public void testCreateUserPrincipal() { @Test public void testNoArgumentsConstructorDefaultScopeAndRoleAuthorities() { - when(jwt.containsClaim("scp")).thenReturn(true); - when(jwt.containsClaim("roles")).thenReturn(true); AADJwtBearerTokenAuthenticationConverter converter = new AADJwtBearerTokenAuthenticationConverter(); AbstractAuthenticationToken authenticationToken = converter.convert(jwt); assertThat(authenticationToken.getPrincipal()).isExactlyInstanceOf(AADOAuth2AuthenticatedPrincipal.class); @@ -65,7 +68,6 @@ public void testNoArgumentsConstructorDefaultScopeAndRoleAuthorities() { @Test public void testNoArgumentsConstructorExtractScopeAuthorities() { - when(jwt.containsClaim("scp")).thenReturn(true); AADJwtBearerTokenAuthenticationConverter converter = new AADJwtBearerTokenAuthenticationConverter(); AbstractAuthenticationToken authenticationToken = converter.convert(jwt); assertThat(authenticationToken.getPrincipal()).isExactlyInstanceOf(AADOAuth2AuthenticatedPrincipal.class); @@ -73,13 +75,12 @@ public void testNoArgumentsConstructorExtractScopeAuthorities() { .getPrincipal(); assertThat(principal.getAttributes()).isNotEmpty(); assertThat(principal.getAttributes()).hasSize(2); - assertThat(principal.getAuthorities()).hasSize(2); + assertThat(principal.getAuthorities()).hasSize(4); } @Test - public void testNoArgumentsConstructorExtractRoleAuthorities() { - when(jwt.containsClaim("roles")).thenReturn(true); - AADJwtBearerTokenAuthenticationConverter converter = new AADJwtBearerTokenAuthenticationConverter(); + public void testParameterConstructorExtractScopeAuthorities() { + AADJwtBearerTokenAuthenticationConverter converter = new AADJwtBearerTokenAuthenticationConverter("scp"); AbstractAuthenticationToken authenticationToken = converter.convert(jwt); assertThat(authenticationToken.getPrincipal()).isExactlyInstanceOf(AADOAuth2AuthenticatedPrincipal.class); AADOAuth2AuthenticatedPrincipal principal = (AADOAuth2AuthenticatedPrincipal) authenticationToken @@ -90,9 +91,9 @@ public void testNoArgumentsConstructorExtractRoleAuthorities() { } @Test - public void testParameterConstructorExtractScopeAuthorities() { - when(jwt.containsClaim("scp")).thenReturn(true); - AADJwtBearerTokenAuthenticationConverter converter = new AADJwtBearerTokenAuthenticationConverter("scp"); + public void testParameterConstructorExtractRoleAuthorities() { + AADJwtBearerTokenAuthenticationConverter converter = new AADJwtBearerTokenAuthenticationConverter("roles", + "APPROLE_"); AbstractAuthenticationToken authenticationToken = converter.convert(jwt); assertThat(authenticationToken.getPrincipal()).isExactlyInstanceOf(AADOAuth2AuthenticatedPrincipal.class); AADOAuth2AuthenticatedPrincipal principal = (AADOAuth2AuthenticatedPrincipal) authenticationToken @@ -103,10 +104,10 @@ public void testParameterConstructorExtractScopeAuthorities() { } @Test - public void testParameterConstructorExtractRoleAuthorities() { - when(jwt.containsClaim("roles")).thenReturn(true); - AADJwtBearerTokenAuthenticationConverter converter = new AADJwtBearerTokenAuthenticationConverter("roles", - "APPROLE_"); + public void testConstructorExtractRoleAuthoritiesWithAuthorityPrefixMapParameter() { + Map claimToAuthorityPrefixMap = new HashMap<>(); + claimToAuthorityPrefixMap.put("roles", "APPROLE_"); + AADJwtBearerTokenAuthenticationConverter converter = new AADJwtBearerTokenAuthenticationConverter("scp", claimToAuthorityPrefixMap); AbstractAuthenticationToken authenticationToken = converter.convert(jwt); assertThat(authenticationToken.getPrincipal()).isExactlyInstanceOf(AADOAuth2AuthenticatedPrincipal.class); AADOAuth2AuthenticatedPrincipal principal = (AADOAuth2AuthenticatedPrincipal) authenticationToken @@ -114,5 +115,7 @@ public void testParameterConstructorExtractRoleAuthorities() { assertThat(principal.getAttributes()).isNotEmpty(); assertThat(principal.getAttributes()).hasSize(2); assertThat(principal.getAuthorities()).hasSize(2); + Assertions.assertTrue(principal.getAuthorities().contains(new SimpleGrantedAuthority("APPROLE_User.read"))); + Assertions.assertTrue(principal.getAuthorities().contains(new SimpleGrantedAuthority("APPROLE_User.write"))); } } diff --git a/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/autoconfigure/b2c/AADB2CUserPrincipalTest.java b/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/autoconfigure/b2c/AADB2CUserPrincipalTest.java index 343fd253badf4..d7b6875d969c5 100644 --- a/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/autoconfigure/b2c/AADB2CUserPrincipalTest.java +++ b/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/autoconfigure/b2c/AADB2CUserPrincipalTest.java @@ -2,6 +2,7 @@ // Licensed under the MIT License. package com.azure.spring.autoconfigure.b2c; +import com.azure.spring.aad.AADOAuth2AuthenticatedPrincipal; import net.minidev.json.JSONArray; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -15,6 +16,7 @@ import java.util.HashMap; import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -49,8 +51,8 @@ public void init() { public void testCreateUserPrincipal() { AADB2CJwtBearerTokenAuthenticationConverter converter = new AADB2CJwtBearerTokenAuthenticationConverter(); AbstractAuthenticationToken authenticationToken = converter.convert(jwt); - Assertions.assertTrue(authenticationToken.getPrincipal().getClass().isAssignableFrom(AADB2COAuth2AuthenticatedPrincipal.class)); - AADB2COAuth2AuthenticatedPrincipal principal = (AADB2COAuth2AuthenticatedPrincipal) authenticationToken + Assertions.assertTrue(authenticationToken.getPrincipal().getClass().isAssignableFrom(AADOAuth2AuthenticatedPrincipal.class)); + AADOAuth2AuthenticatedPrincipal principal = (AADOAuth2AuthenticatedPrincipal) authenticationToken .getPrincipal(); Assertions.assertFalse(principal.getClaims().isEmpty()); Assertions.assertEquals(principal.getIssuer(), claims.get("iss")); @@ -59,12 +61,10 @@ public void testCreateUserPrincipal() { @Test public void testNoArgumentsConstructorDefaultScopeAndRoleAuthorities() { - when(jwt.containsClaim("scp")).thenReturn(true); - when(jwt.containsClaim("roles")).thenReturn(true); AADB2CJwtBearerTokenAuthenticationConverter converter = new AADB2CJwtBearerTokenAuthenticationConverter(); AbstractAuthenticationToken authenticationToken = converter.convert(jwt); - Assertions.assertTrue(authenticationToken.getPrincipal().getClass().isAssignableFrom(AADB2COAuth2AuthenticatedPrincipal.class)); - AADB2COAuth2AuthenticatedPrincipal principal = (AADB2COAuth2AuthenticatedPrincipal) authenticationToken + Assertions.assertTrue(authenticationToken.getPrincipal().getClass().isAssignableFrom(AADOAuth2AuthenticatedPrincipal.class)); + AADOAuth2AuthenticatedPrincipal principal = (AADOAuth2AuthenticatedPrincipal) authenticationToken .getPrincipal(); Assertions.assertFalse(principal.getAttributes().isEmpty()); Assertions.assertTrue(principal.getAttributes().size() == 2); @@ -75,11 +75,11 @@ public void testNoArgumentsConstructorDefaultScopeAndRoleAuthorities() { @Test public void testNoArgumentsConstructorExtractScopeAuthorities() { - when(jwt.containsClaim("scp")).thenReturn(true); + when(jwt.getClaim("roles")).thenReturn(null); AADB2CJwtBearerTokenAuthenticationConverter converter = new AADB2CJwtBearerTokenAuthenticationConverter(); AbstractAuthenticationToken authenticationToken = converter.convert(jwt); - Assertions.assertTrue(authenticationToken.getPrincipal().getClass().isAssignableFrom(AADB2COAuth2AuthenticatedPrincipal.class)); - AADB2COAuth2AuthenticatedPrincipal principal = (AADB2COAuth2AuthenticatedPrincipal) authenticationToken + Assertions.assertTrue(authenticationToken.getPrincipal().getClass().isAssignableFrom(AADOAuth2AuthenticatedPrincipal.class)); + AADOAuth2AuthenticatedPrincipal principal = (AADOAuth2AuthenticatedPrincipal) authenticationToken .getPrincipal(); Assertions.assertFalse(principal.getAttributes().isEmpty()); Assertions.assertTrue(principal.getAttributes().size() == 2); @@ -92,11 +92,11 @@ public void testNoArgumentsConstructorExtractScopeAuthorities() { @Test public void testNoArgumentsConstructorExtractRoleAuthorities() { - when(jwt.containsClaim("roles")).thenReturn(true); + when(jwt.getClaim("scp")).thenReturn(null); AADB2CJwtBearerTokenAuthenticationConverter converter = new AADB2CJwtBearerTokenAuthenticationConverter(); AbstractAuthenticationToken authenticationToken = converter.convert(jwt); - Assertions.assertTrue(authenticationToken.getPrincipal().getClass().isAssignableFrom(AADB2COAuth2AuthenticatedPrincipal.class)); - AADB2COAuth2AuthenticatedPrincipal principal = (AADB2COAuth2AuthenticatedPrincipal) authenticationToken + Assertions.assertTrue(authenticationToken.getPrincipal().getClass().isAssignableFrom(AADOAuth2AuthenticatedPrincipal.class)); + AADOAuth2AuthenticatedPrincipal principal = (AADOAuth2AuthenticatedPrincipal) authenticationToken .getPrincipal(); Assertions.assertFalse(principal.getAttributes().isEmpty()); Assertions.assertTrue(principal.getAttributes().size() == 2); @@ -107,13 +107,30 @@ public void testNoArgumentsConstructorExtractRoleAuthorities() { Assertions.assertFalse(principal.getAuthorities().contains(new SimpleGrantedAuthority("SCOPE_Order.write"))); } + @Test + public void testConstructorExtractRoleAuthoritiesWithAuthorityPrefixMapParameter() { + when(jwt.getClaim("scp")).thenReturn(null); + Map claimToAuthorityPrefixMap = new HashMap<>(); + claimToAuthorityPrefixMap.put("roles", "APPROLE_"); + AADB2CJwtBearerTokenAuthenticationConverter converter = new AADB2CJwtBearerTokenAuthenticationConverter("sub", claimToAuthorityPrefixMap); + AbstractAuthenticationToken authenticationToken = converter.convert(jwt); + assertThat(authenticationToken.getPrincipal()).isExactlyInstanceOf(AADOAuth2AuthenticatedPrincipal.class); + AADOAuth2AuthenticatedPrincipal principal = (AADOAuth2AuthenticatedPrincipal) authenticationToken + .getPrincipal(); + assertThat(principal.getAttributes()).isNotEmpty(); + assertThat(principal.getAttributes()).hasSize(2); + assertThat(principal.getAuthorities()).hasSize(2); + Assertions.assertTrue(principal.getAuthorities().contains(new SimpleGrantedAuthority("APPROLE_User.read"))); + Assertions.assertTrue(principal.getAuthorities().contains(new SimpleGrantedAuthority("APPROLE_User.write"))); + } + @Test public void testParameterConstructorExtractScopeAuthorities() { - when(jwt.containsClaim("scp")).thenReturn(true); + when(jwt.getClaim("roles")).thenReturn(null); AADB2CJwtBearerTokenAuthenticationConverter converter = new AADB2CJwtBearerTokenAuthenticationConverter("scp"); AbstractAuthenticationToken authenticationToken = converter.convert(jwt); - Assertions.assertTrue(authenticationToken.getPrincipal().getClass().isAssignableFrom(AADB2COAuth2AuthenticatedPrincipal.class)); - AADB2COAuth2AuthenticatedPrincipal principal = (AADB2COAuth2AuthenticatedPrincipal) authenticationToken + Assertions.assertTrue(authenticationToken.getPrincipal().getClass().isAssignableFrom(AADOAuth2AuthenticatedPrincipal.class)); + AADOAuth2AuthenticatedPrincipal principal = (AADOAuth2AuthenticatedPrincipal) authenticationToken .getPrincipal(); Assertions.assertFalse(principal.getAttributes().isEmpty()); Assertions.assertTrue(principal.getAttributes().size() == 2); @@ -126,12 +143,12 @@ public void testParameterConstructorExtractScopeAuthorities() { @Test public void testParameterConstructorExtractRoleAuthorities() { - when(jwt.containsClaim("roles")).thenReturn(true); + when(jwt.getClaim("scp")).thenReturn(null); AADB2CJwtBearerTokenAuthenticationConverter converter = new AADB2CJwtBearerTokenAuthenticationConverter("roles", "APPROLE_"); AbstractAuthenticationToken authenticationToken = converter.convert(jwt); - Assertions.assertTrue(authenticationToken.getPrincipal().getClass().isAssignableFrom(AADB2COAuth2AuthenticatedPrincipal.class)); - AADB2COAuth2AuthenticatedPrincipal principal = (AADB2COAuth2AuthenticatedPrincipal) authenticationToken + Assertions.assertTrue(authenticationToken.getPrincipal().getClass().isAssignableFrom(AADOAuth2AuthenticatedPrincipal.class)); + AADOAuth2AuthenticatedPrincipal principal = (AADOAuth2AuthenticatedPrincipal) authenticationToken .getPrincipal(); Assertions.assertFalse(principal.getAttributes().isEmpty()); Assertions.assertTrue(principal.getAttributes().size() == 2);