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 13 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 @@ -2,7 +2,10 @@

## 3.6.0-beta.1 (Unreleased)
### 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 @@ -2,8 +2,10 @@

## 3.6.0-beta.1 (Unreleased)
### 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].
...
```

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
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 @@ -2,7 +2,9 @@

## 3.6.0-beta.1 (Unreleased)
### 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 @@ -2,7 +2,9 @@

## 3.6.0-beta.1 (Unreleased)
### 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,79 @@
// 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(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