Skip to content

Commit

Permalink
[FEATURE REQ] Make it easier to debug aad-starter (Azure#22289)
Browse files Browse the repository at this point in the history
  • Loading branch information
Moary Chen authored Jun 30, 2021
1 parent ff2320a commit c848f61
Show file tree
Hide file tree
Showing 22 changed files with 352 additions and 374 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
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 @@ -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))
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].
...
```

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
4 changes: 3 additions & 1 deletion sdk/spring/azure-spring-boot-starter-cosmos/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<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";
public static final String DEFAULT_AUTHORITY_PREFIX = "SCOPE_";

static {
Map<String, String> 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<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,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<Jwt,
AbstractAuthenticationToken> {

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

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

protected static Map<String, String> buildClaimToAuthorityPrefixMap(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);
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<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

0 comments on commit c848f61

Please sign in to comment.