Skip to content
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

[FEATURE REQ] Make it easier to debug aad-starter #22289

Merged
merged 16 commits into from
Jun 30, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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, and you will see the user's authorities based on the log.
chenrujun marked this conversation as resolved.
Show resolved Hide resolved

```properties
# logging settings for resource server scenario.
logging.level.com.azure.spring.common.AADJwtGrantedAuthoritiesConverter=DEBUG
chenrujun marked this conversation as resolved.
Show resolved Hide resolved
```

The log output as a resource server usage:
chenrujun marked this conversation as resolved.
Show resolved Hide resolved

```text
...
DEBUG .a.s.c.AADJwtGrantedAuthoritiesConverter : User TestUser's authorities created from jwt token: [SCOPE_Test.Read, APPROLE_WebApi.ExampleScope].
...
```

## Next steps

## Contributing
Expand Down
28 changes: 28 additions & 0 deletions sdk/spring/azure-spring-boot-starter-active-directory/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,34 @@ 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, and you will see the user's authorities based on the log.
chenrujun marked this conversation as resolved.
Show resolved Hide resolved

```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.common.AADJwtGrantedAuthoritiesConverter=DEBUG
```

The log output as a web application usage:
chenrujun marked this conversation as resolved.
Show resolved Hide resolved

```text
...
DEBUG c.a.s.aad.webapp.AADOAuth2UserService : User TestUser's authorities extracted by id token and access token: [ROLE_group1, ROLE_group2].
...
```

chenrujun marked this conversation as resolved.
Show resolved Hide resolved
The log output as a resource server usage:

```text
...
DEBUG .a.s.c.AADJwtGrantedAuthoritiesConverter : User TestUser's authorities created from jwt token: [SCOPE_Test.Read, APPROLE_WebApi.ExampleScope].
...
```

## Next steps

## Contributing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,29 @@
// Licensed under the MIT License.
package com.azure.spring.aad.webapi;

import com.azure.spring.common.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;

/**
* A {@link Converter} that takes a {@link Jwt} and converts it into a {@link BearerTokenAuthentication}.
*/
public class AADJwtBearerTokenAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {
public class AADJwtBearerTokenAuthenticationConverter extends AbstractJwtBearerTokenAuthenticationConverter {

private static final String DEFAULT_AUTHORITY_PREFIX = "SCOPE_";
private static final String DEFAULT_PRINCIPAL_CLAIM_NAME = "sub";

private Converter<Jwt, Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter;
private String principalClaimName = DEFAULT_PRINCIPAL_CLAIM_NAME;

/**
* Use AADJwtGrantedAuthoritiesConverter, it can resolve the access token of scp and roles.
chenrujun marked this conversation as resolved.
Show resolved Hide resolved
*/
public AADJwtBearerTokenAuthenticationConverter() {
this.jwtGrantedAuthoritiesConverter = new AADJwtGrantedAuthoritiesConverter();
super();
principalClaimName = DEFAULT_PRINCIPAL_CLAIM_NAME;
chenrujun marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand All @@ -41,35 +37,14 @@ public AADJwtBearerTokenAuthenticationConverter(String authoritiesClaimName) {
}

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;
}

protected Collection<GrantedAuthority> extractAuthorities(Jwt jwt) {
return this.jwtGrantedAuthoritiesConverter.convert(jwt);
super(authoritiesClaimName, authorityPrefix);
chenrujun marked this conversation as resolved.
Show resolved Hide resolved
principalClaimName = DEFAULT_PRINCIPAL_CLAIM_NAME;
}

@Override
public AbstractAuthenticationToken convert(Jwt jwt) {
OAuth2AccessToken accessToken = new OAuth2AccessToken(
OAuth2AccessToken.TokenType.BEARER, jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt());
Collection<GrantedAuthority> 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<Jwt, Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter) {
this.jwtGrantedAuthoritiesConverter = jwtGrantedAuthoritiesConverter;
}

public void setPrincipalClaimName(String principalClaimName) {
Assert.hasText(principalClaimName, "principalClaimName cannot be empty");
this.principalClaimName = principalClaimName;
protected OAuth2AuthenticatedPrincipal getAuthenticatedPrincipal(Map<String, Object> headers,
Map<String, Object> claims, Collection<GrantedAuthority> authorities, String tokenValue) {
return new AADOAuth2AuthenticatedPrincipal(
headers, claims, authorities, tokenValue, principalClaimName);
chenrujun marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -44,6 +46,8 @@
*/
public class AADOAuth2UserService implements OAuth2UserService<OidcUserRequest, OidcUser> {

private static final Logger LOGGER = LoggerFactory.getLogger(AADOAuth2UserService.class);

private final OidcUserService oidcUserService;
private final List<String> allowedGroupNames;
private final Set<String> allowedGroupIds;
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,26 @@
// Licensed under the MIT License.
package com.azure.spring.autoconfigure.b2c;

import com.azure.spring.common.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;

/**
* A {@link Converter} that takes a {@link Jwt} and converts it into a {@link BearerTokenAuthentication}.
*/
public class AADB2CJwtBearerTokenAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {

private static final String DEFAULT_AUTHORITY_PREFIX = "SCOPE_";

private Converter<Jwt, Collection<GrantedAuthority>> converter;

private String principalClaimName;
public class AADB2CJwtBearerTokenAuthenticationConverter extends AbstractJwtBearerTokenAuthenticationConverter {

/**
* Use AADB2CJwtGrantedAuthoritiesConverter, it can resolve the access token of scp and roles.
* Use AADJwtGrantedAuthoritiesConverter, it can resolve the access token of scp and roles.
chenrujun marked this conversation as resolved.
Show resolved Hide resolved
*/
public AADB2CJwtBearerTokenAuthenticationConverter() {
this.converter = new AADB2CJwtGrantedAuthoritiesConverter();
super();
chenrujun marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand All @@ -40,41 +33,18 @@ public AADB2CJwtBearerTokenAuthenticationConverter(String authoritiesClaimName)
}

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;
}

protected Collection<GrantedAuthority> extractAuthorities(Jwt jwt) {
return this.converter.convert(jwt);
super(authoritiesClaimName, authorityPrefix);
chenrujun marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
public AbstractAuthenticationToken convert(Jwt jwt) {
OAuth2AccessToken accessToken = new OAuth2AccessToken(
OAuth2AccessToken.TokenType.BEARER, jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt());
Collection<GrantedAuthority> authorities = extractAuthorities(jwt);
protected OAuth2AuthenticatedPrincipal getAuthenticatedPrincipal(Map<String, Object> headers,
Map<String, Object> claims, Collection<GrantedAuthority> authorities, String tokenValue) {
if (this.principalClaimName == null) {
AADB2COAuth2AuthenticatedPrincipal principal = new AADB2COAuth2AuthenticatedPrincipal(
jwt.getHeaders(), jwt.getClaims(), authorities, jwt.getTokenValue());
return new BearerTokenAuthentication(principal, accessToken, authorities);
return new AADB2COAuth2AuthenticatedPrincipal(
headers, claims, authorities, tokenValue);
}
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<Jwt, Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter) {
this.converter = jwtGrantedAuthoritiesConverter;
}

public void setPrincipalClaimName(String principalClaimName) {
Assert.hasText(principalClaimName, "principalClaimName cannot be empty");
this.principalClaimName = principalClaimName;
String name = (String) claims.get(this.principalClaimName);
return new AADB2COAuth2AuthenticatedPrincipal(headers,
claims, authorities, tokenValue, name);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.spring.aad.webapi;
package com.azure.spring.common;

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;
Expand All @@ -18,6 +20,8 @@
*/
public class AADJwtGrantedAuthoritiesConverter implements Converter<Jwt, Collection<GrantedAuthority>> {

private static final Logger LOGGER = LoggerFactory.getLogger(AADJwtGrantedAuthoritiesConverter.class);

private static final String DEFAULT_SCP_AUTHORITY_PREFIX = "SCOPE_";

private static final String DEFAULT_ROLES_AUTHORITY_PREFIX = "APPROLE_";
Expand All @@ -30,13 +34,14 @@ public Collection<GrantedAuthority> convert(Jwt jwt) {
for (String authority : getAuthorities(jwt)) {
grantedAuthorities.add(new SimpleGrantedAuthority(authority));
}
LOGGER.debug("User {}'s authorities created from jwt token: {}.", jwt.getSubject(), grantedAuthorities);
return grantedAuthorities;
}

private Collection<String> getAuthorities(Jwt jwt) {
Collection<String> authoritiesList = new ArrayList<String>();
for (String claimName : WELL_KNOWN_AUTHORITIES_CLAIM_NAMES) {
if (jwt.containsClaim(claimName)) {
if (jwt.hasClaim(claimName)) {
if (jwt.getClaim(claimName) instanceof String) {
if (StringUtils.hasText(jwt.getClaim(claimName))) {
authoritiesList.addAll(Arrays.asList(((String) jwt.getClaim(claimName)).split(" "))
Expand Down
Loading