Skip to content

Commit

Permalink
For AAD resource-server, create grantedAuthority by both "roles" and …
Browse files Browse the repository at this point in the history
…"claims" by default. (#19412)
  • Loading branch information
ZhuXiaoBing-cn authored Mar 5, 2021
1 parent fdb49c9 commit 0373b50
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 15 deletions.
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);
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);
}

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

0 comments on commit 0373b50

Please sign in to comment.