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

For AAD resource-server, create grantedAuthority by both "roles" and "claims" by default. #19412

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT License.
package com.azure.spring.aad.webapi;

import java.util.Collection;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
Expand All @@ -12,17 +11,19 @@
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.util.Assert;

import java.util.Collection;

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

private static final String DEFAULT_AUTHORITY_PREFIX = "SCOPE_";

private Converter<Jwt, Collection<GrantedAuthority>> converter
= new JwtGrantedAuthoritiesConverter();
private Converter<Jwt, Collection<GrantedAuthority>> converter;

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

public AADJwtBearerTokenAuthenticationConverter(String authoritiesClaimName) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// 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<Jwt, Collection<GrantedAuthority>> {

private static final String DEFAULT_SCP_AUTHORITY_PREFIX = "SCOPE_";

private static final String DEFAULT_ROLES_AUTHORITY_PREFIX = "APPROLE_";

private static final Collection<String> WELL_KNOWN_AUTHORITIES_CLAIM_NAMES = Arrays.asList("scp", "roles");

@Override
public Collection<GrantedAuthority> convert(Jwt jwt) {
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
for (String authority : getAuthorities(jwt)) {
grantedAuthorities.add(new SimpleGrantedAuthority(authority));
}
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.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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,39 @@
// Licensed under the MIT License.
package com.azure.spring.aad.webapi;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import net.minidev.json.JSONArray;
import org.junit.Before;
import org.junit.Test;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.oauth2.jwt.Jwt;

import java.time.Instant;
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;

public class AADJwtBearerTokenAuthenticationConverterTest {

private Jwt jwt = mock(Jwt.class);
private Map<String, Object> claims = new HashMap<>();
private Map<String, Object> headers = new HashMap<>();
private JSONArray jsonArray = new JSONArray().appendElement("User.read").appendElement("User.write");

@Before
public void init() {
claims.put("iss", "fake-issuer");
claims.put("tid", "fake-tid");
headers.put("kid", "kg2LYs2T0CTjIfj4rt6JIynen38");
when(jwt.getClaim("scp")).thenReturn("Order.read Order.write");
when(jwt.getClaim("roles")).thenReturn("User.read User.write");
when(jwt.getClaim("roles")).thenReturn(jsonArray);
saragluna marked this conversation as resolved.
Show resolved Hide resolved
when(jwt.getTokenValue()).thenReturn("fake-token-value");
when(jwt.getIssuedAt()).thenReturn(Instant.now());
when(jwt.getHeaders()).thenReturn(headers);
when(jwt.getExpiresAt()).thenReturn(Instant.MAX);
when(jwt.getClaims()).thenReturn(claims);
when(jwt.containsClaim("scp")).thenReturn(true);
}

@Test
Expand All @@ -48,27 +50,69 @@ public void testCreateUserPrincipal() {
}

@Test
public void testExtractDefaultScopeAuthorities() {
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);
AADOAuth2AuthenticatedPrincipal principal = (AADOAuth2AuthenticatedPrincipal) authenticationToken
.getPrincipal();
assertThat(principal.getAttributes()).isNotEmpty();
assertThat(principal.getAttributes()).hasSize(2);
assertThat(principal.getAuthorities()).hasSize(4);
}

@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);
AADOAuth2AuthenticatedPrincipal principal = (AADOAuth2AuthenticatedPrincipal) authenticationToken
.getPrincipal();
assertThat(principal.getAttributes()).isNotEmpty();
assertThat(principal.getAttributes()).hasSize(2);
assertThat(principal.getAuthorities()).hasSize(2);
saragluna marked this conversation as resolved.
Show resolved Hide resolved
}

@Test
public void testExtractCustomScopeAuthorities() {
public void testNoArgumentsConstructorExtractRoleAuthorities() {
when(jwt.containsClaim("roles")).thenReturn(true);
AADJwtBearerTokenAuthenticationConverter converter = new AADJwtBearerTokenAuthenticationConverter("roles", "ROLE_");
AADJwtBearerTokenAuthenticationConverter converter = new AADJwtBearerTokenAuthenticationConverter();
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);
}

@Test
public void testParameterConstructorExtractScopeAuthorities() {
when(jwt.containsClaim("scp")).thenReturn(true);
AADJwtBearerTokenAuthenticationConverter converter = new AADJwtBearerTokenAuthenticationConverter("scp");
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);
}

@Test
public void testParameterConstructorExtractRoleAuthorities() {
when(jwt.containsClaim("roles")).thenReturn(true);
AADJwtBearerTokenAuthenticationConverter converter = new AADJwtBearerTokenAuthenticationConverter("roles",
"APPROLE_");
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);
}
}