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 7 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 @@ -3,6 +3,9 @@
## 3.6.0-beta.1 (Unreleased)
### Breaking Changes
- Deprecated `allowTelemetry` configuration item.
### Breaking Changes
chenrujun marked this conversation as resolved.
Show resolved Hide resolved
- Deprecate the two construction methods of class AADB2CJwtBearerTokenAuthenticationConverter.
chenrujun marked this conversation as resolved.
Show resolved Hide resolved
- Remove class `AADB2COAuth2AuthenticatedPrincipal`, use class `AADOAuth2AuthenticatedPrincipal` instead.

## 3.5.0 (2021-05-24)
### New Features
Expand Down
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:

```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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
### Breaking Changes
- Deprecate aad.group.enable-full-list, use aad.group.allowed-group-ids=all instead.
- Deprecated `allowTelemetry` configuration item.
- Deprecate the two construction methods of class `AADJwtBearerTokenAuthenticationConverter`.
chenrujun marked this conversation as resolved.
Show resolved Hide resolved

### New Features
- Support domain_hint in aad-starter.([#21517](https://github.com/Azure/azure-sdk-for-java/issues/21517))
Expand Down
30 changes: 30 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,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].
...
```

chenrujun marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// 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<Jwt, Collection<GrantedAuthority>> {

private static final Logger LOGGER = LoggerFactory.getLogger(AADJwtGrantedAuthoritiesConverter.class);
public static final Map<String, String> DEFAULT_CLAIM_TO_AUTHORITY_PREFIX_MAP;
public static final String DEFAULT_AUTHORITY_CLAIM_NAME = "scp";

static {
Map<String, String> claimAuthorityMap = new HashMap<>();
claimAuthorityMap.put(DEFAULT_AUTHORITY_CLAIM_NAME, "SCOPE_");
claimAuthorityMap.put("roles", "APPROLE_");
DEFAULT_CLAIM_TO_AUTHORITY_PREFIX_MAP = Collections.unmodifiableMap(claimAuthorityMap);
}

private final Map<String, String> claimToAuthorityPrefixMap;

public AADJwtGrantedAuthoritiesConverter() {
claimToAuthorityPrefixMap = DEFAULT_CLAIM_TO_AUTHORITY_PREFIX_MAP;
}

public AADJwtGrantedAuthoritiesConverter(Map<String, String> claimToAuthorityPrefixMap) {
this.claimToAuthorityPrefixMap = claimToAuthorityPrefixMap;
}

@Override
public Collection<GrantedAuthority> convert(Jwt jwt) {
Collection<GrantedAuthority> 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();
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 {

Expand All @@ -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<String, Object> headers,
Map<String, Object> attributes,
Collection<GrantedAuthority> 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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// 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;

import static com.azure.spring.aad.AADJwtGrantedAuthoritiesConverter.DEFAULT_CLAIM_TO_AUTHORITY_PREFIX_MAP;

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

public static final String DEFAULT_PRINCIPAL_CLAIM_NAME = "sub";
protected Converter<Jwt, Collection<GrantedAuthority>> converter;
protected String principalClaimName;

public AbstractJwtBearerTokenAuthenticationConverter() {
this.converter = new AADJwtGrantedAuthoritiesConverter(DEFAULT_CLAIM_TO_AUTHORITY_PREFIX_MAP);
}

/**
* Construct jwt token converter.
* @param authoritiesClaimName authority claim name
* @param authorityPrefix the prefix name of the authority
* @deprecated Recommended to use others constructor.
*/
@Deprecated
public AbstractJwtBearerTokenAuthenticationConverter(String authoritiesClaimName,
String authorityPrefix) {
Assert.notNull(authoritiesClaimName, "authoritiesClaimName cannot be null");
Assert.notNull(authorityPrefix, "authorityPrefix cannot be null");
Map<String, String> claimToAuthorityPrefixMap = new HashMap<>();
claimToAuthorityPrefixMap.put(authoritiesClaimName, authorityPrefix);
this.converter = new AADJwtGrantedAuthoritiesConverter(claimToAuthorityPrefixMap);
}

public AbstractJwtBearerTokenAuthenticationConverter(String principalClaimName,
Map<String, String> claimToAuthorityPrefixMap) {
Assert.notNull(principalClaimName, "principalClaimName cannot be null");
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<GrantedAuthority> authorities = converter.convert(jwt);
OAuth2AuthenticatedPrincipal principal = getAuthenticatedPrincipal(
jwt.getHeaders(), jwt.getClaims(), authorities, jwt.getTokenValue());
return new BearerTokenAuthentication(principal, accessToken, authorities);
}

/**
* 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<String, Object> headers,
Map<String, Object> claims,
Collection<GrantedAuthority> authorities,
String tokenValue);

public void setConverter(
Converter<Jwt, Collection<GrantedAuthority>> converter) {
this.converter = converter;
}

public void setPrincipalClaimName(String principalClaimName) {
Assert.hasText(principalClaimName, "principalClaimName cannot be empty");
this.principalClaimName = principalClaimName;
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Loading