Skip to content

JwtGrantedAuthoritiesConverter allow configuring the authorities claim name #7104

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all 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 @@ -16,10 +16,11 @@

package org.springframework.security.oauth2.server.resource.authentication;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.GrantedAuthority;
Expand All @@ -28,17 +29,44 @@
import org.springframework.util.StringUtils;

/**
* Extracts the {@link GrantedAuthority}s from scope attributes typically found in a
* Extracts the {@link GrantedAuthority}s from scope claims typically found in a
* {@link Jwt}.
*
* @author Jérôme Wacongne <ch4mp@c4-soft.com>
* @author Eric Deandrea
* @since 5.2
*/
public final class JwtGrantedAuthoritiesConverter implements Converter<Jwt, Collection<GrantedAuthority>> {
private static final String SCOPE_AUTHORITY_PREFIX = "SCOPE_";
private static final String DEFAULT_AUTHORITIES_PREFIX = "SCOPE_";

private static final Collection<String> WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES =
Arrays.asList("scope", "scp");
private static final Set<String> WELL_KNOWN_SCOPE_CLAIM_NAMES =
Stream.of("scope", "scp").collect(Collectors.toSet());

private Set<String> authoritiesClaimNames;

public JwtGrantedAuthoritiesConverter(Collection<String> authoritiesClaimNames) {
super();
this.authoritiesClaimNames = new HashSet<>(authoritiesClaimNames);
}

public JwtGrantedAuthoritiesConverter() {
this(WELL_KNOWN_SCOPE_CLAIM_NAMES);
}

public JwtGrantedAuthoritiesConverter addAuthoritiesClaimName(String authoritiesClaimName) {
this.authoritiesClaimNames.add(authoritiesClaimName);
return this;
}

public JwtGrantedAuthoritiesConverter setAuthoritiesClaimNames(String... authoritiesClaimNames) {
this.authoritiesClaimNames = Stream.of(authoritiesClaimNames).collect(Collectors.toSet());
return this;
}

public JwtGrantedAuthoritiesConverter setAuthoritiesClaimNames(Collection<String> authoritiesClaimNames) {
this.authoritiesClaimNames = authoritiesClaimNames.stream().collect(Collectors.toSet());
return this;
}

/**
* Extracts the authorities
Expand All @@ -47,32 +75,26 @@ public final class JwtGrantedAuthoritiesConverter implements Converter<Jwt, Coll
*/
@Override
public Collection<GrantedAuthority> convert(Jwt jwt) {
return getScopes(jwt)
.stream()
.map(authority -> SCOPE_AUTHORITY_PREFIX + authority)
return authoritiesClaimNames.stream()
.flatMap(claimName -> getAuthorities(jwt, claimName))
.map(authority -> DEFAULT_AUTHORITIES_PREFIX + authority)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
.collect(Collectors.toSet());
}

/**
* Gets the scopes from a {@link Jwt} token
* @param jwt The {@link Jwt} token
* @return The scopes from the token
*/
private Collection<String> getScopes(Jwt jwt) {
for ( String attributeName : WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES ) {
Object scopes = jwt.getClaims().get(attributeName);
if (scopes instanceof String) {
if (StringUtils.hasText((String) scopes)) {
return Arrays.asList(((String) scopes).split(" "));
} else {
return Collections.emptyList();
}
} else if (scopes instanceof Collection) {
return (Collection<String>) scopes;
@SuppressWarnings("unchecked")
private Stream<String> getAuthorities(Jwt jwt, String claimName) {
Object authorities = jwt.getClaim(claimName);
if (authorities instanceof String) {
if (StringUtils.hasText((String) authorities)) {
return Stream.of(((String) authorities).split(" "));
} else {
return Stream.empty();
}
} else if (authorities instanceof Collection) {
return ((Collection<String>) authorities).stream();
}

return Collections.emptyList();
return Stream.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import java.util.Map;

import org.junit.Test;

import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
Expand All @@ -41,16 +40,22 @@
* @author Josh Cummings
*/
public class JwtAuthenticationConverterTests {
JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter();
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();

public JwtAuthenticationConverterTests() {
authoritiesConverter.addAuthoritiesClaimName("authorities");
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
}

@Test
public void convertWhenDefaultGrantedAuthoritiesConverterSet() {
Jwt jwt = this.jwt(Collections.singletonMap("scope", "message:read message:write"));

AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt);
Collection<GrantedAuthority> authorities = authentication.getAuthorities();

assertThat(authorities).containsExactly(
assertThat(authorities).containsExactlyInAnyOrder(
new SimpleGrantedAuthority("SCOPE_message:read"),
new SimpleGrantedAuthority("SCOPE_message:write"));
}
Expand All @@ -74,10 +79,28 @@ public void convertWithOverriddenGrantedAuthoritiesConverter() {
AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt);
Collection<GrantedAuthority> authorities = authentication.getAuthorities();

assertThat(authorities).containsExactly(
assertThat(authorities).containsExactlyInAnyOrder(
new SimpleGrantedAuthority("blah"));
}

@Test
public void convertWhenTokenHasAttributesClaimsWithDifferentPrefixesThenPrefixesAreCorrectlyApplied( ) {
Map<String, Object> claims = new HashMap<>();
claims.put("authorities", Arrays.asList("ROLE_USER", "ROLE_ADMIN"));
claims.put("scope", "missive:read missive:write");
Jwt jwt = this.jwt(claims);

AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt);

Collection<GrantedAuthority> authorities = authentication.getAuthorities();

assertThat(authorities).containsExactlyInAnyOrder(
new SimpleGrantedAuthority("SCOPE_ROLE_USER"),
new SimpleGrantedAuthority("SCOPE_ROLE_ADMIN"),
new SimpleGrantedAuthority("SCOPE_missive:read"),
new SimpleGrantedAuthority("SCOPE_missive:write"));
}

private Jwt jwt(Map<String, Object> claims) {
Map<String, Object> headers = new HashMap<>();
headers.put("alg", JwsAlgorithms.RS256);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@

import org.assertj.core.util.Maps;
import org.junit.Test;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
Expand All @@ -40,15 +39,16 @@
* @since 5.2
*/
public class JwtGrantedAuthoritiesConverterTests {
private JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
private JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter =
new JwtGrantedAuthoritiesConverter().addAuthoritiesClaimName("authorities");

@Test
public void convertWhenTokenHasScopeAttributeThenTranslatedToAuthorities() {
Jwt jwt = this.jwt(Collections.singletonMap("scope", "message:read message:write"));

Collection<GrantedAuthority> authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt);

assertThat(authorities).containsExactly(
assertThat(authorities).containsExactlyInAnyOrder(
new SimpleGrantedAuthority("SCOPE_message:read"),
new SimpleGrantedAuthority("SCOPE_message:write"));
}
Expand All @@ -59,7 +59,7 @@ public void convertWhenTokenHasEmptyScopeAttributeThenTranslatedToNoAuthorities(

Collection<GrantedAuthority> authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt);

assertThat(authorities).containsExactly();
assertThat(authorities).containsExactlyInAnyOrder();
}

@Test
Expand All @@ -68,7 +68,7 @@ public void convertWhenTokenHasScpAttributeThenTranslatedToAuthorities() {

Collection<GrantedAuthority> authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt);

assertThat(authorities).containsExactly(
assertThat(authorities).containsExactlyInAnyOrder(
new SimpleGrantedAuthority("SCOPE_message:read"),
new SimpleGrantedAuthority("SCOPE_message:write"));
}
Expand All @@ -79,35 +79,25 @@ public void convertWhenTokenHasEmptyScpAttributeThenTranslatedToNoAuthorities()

Collection<GrantedAuthority> authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt);

assertThat(authorities).containsExactly();
assertThat(authorities).containsExactlyInAnyOrder();
}

@Test
public void convertWhenTokenHasBothScopeAndScpThenScopeAttributeIsTranslatedToAuthorities() {
public void convertWhenTokenHasBothScopeAndScpThenBothAttributesAreTranslatedToAuthorities() {
Map<String, Object> claims = new HashMap<>();
claims.put("scp", Arrays.asList("message:read", "message:write"));
claims.put("scope", "missive:read missive:write");
Jwt jwt = this.jwt(claims);

Collection<GrantedAuthority> authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt);

assertThat(authorities).containsExactly(
assertThat(authorities).containsExactlyInAnyOrder(
new SimpleGrantedAuthority("SCOPE_message:read"),
new SimpleGrantedAuthority("SCOPE_message:write"),
new SimpleGrantedAuthority("SCOPE_missive:read"),
new SimpleGrantedAuthority("SCOPE_missive:write"));
}

@Test
public void convertWhenTokenHasEmptyScopeAndNonEmptyScpThenScopeAttributeIsTranslatedToNoAuthorities() {
Map<String, Object> claims = new HashMap<>();
claims.put("scp", Arrays.asList("message:read", "message:write"));
claims.put("scope", "");
Jwt jwt = this.jwt(claims);

Collection<GrantedAuthority> authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt);

assertThat(authorities).containsExactly();
}

private Jwt jwt(Map<String, Object> claims) {
Map<String, Object> headers = new HashMap<>();
headers.put("alg", JwsAlgorithms.RS256);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package org.springframework.security.oauth2.server.resource.authentication;

import static org.assertj.core.api.Assertions.assertThat;

import java.time.Instant;
import java.util.Arrays;
import java.util.Collection;
Expand All @@ -24,16 +26,13 @@
import java.util.Map;

import org.junit.Test;

import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
import org.springframework.security.oauth2.jwt.Jwt;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Tests for {@link ReactiveJwtAuthenticationConverterAdapter}
*
Expand All @@ -51,7 +50,7 @@ public void convertWhenTokenHasScopeAttributeThenTranslatedToAuthorities() {
AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt).block();
Collection<GrantedAuthority> authorities = authentication.getAuthorities();

assertThat(authorities).containsExactly(
assertThat(authorities).containsExactlyInAnyOrder(
new SimpleGrantedAuthority("SCOPE_message:read"),
new SimpleGrantedAuthority("SCOPE_message:write"));
}
Expand All @@ -64,7 +63,7 @@ public void convertWhenTokenHasEmptyScopeAttributeThenTranslatedToNoAuthorities(

Collection<GrantedAuthority> authorities = authentication.getAuthorities();

assertThat(authorities).containsExactly();
assertThat(authorities).containsExactlyInAnyOrder();
}

@Test
Expand All @@ -75,7 +74,7 @@ public void convertWhenTokenHasScpAttributeThenTranslatedToAuthorities() {

Collection<GrantedAuthority> authorities = authentication.getAuthorities();

assertThat(authorities).containsExactly(
assertThat(authorities).containsExactlyInAnyOrder(
new SimpleGrantedAuthority("SCOPE_message:read"),
new SimpleGrantedAuthority("SCOPE_message:write"));
}
Expand All @@ -88,11 +87,11 @@ public void convertWhenTokenHasEmptyScpAttributeThenTranslatedToNoAuthorities()

Collection<GrantedAuthority> authorities = authentication.getAuthorities();

assertThat(authorities).containsExactly();
assertThat(authorities).containsExactlyInAnyOrder();
}

@Test
public void convertWhenTokenHasBothScopeAndScpThenScopeAttributeIsTranslatedToAuthorities() {
public void convertWhenTokenHasBothScopeAndScpThenBothAttributesAreTranslatedToAuthorities() {
Map<String, Object> claims = new HashMap<>();
claims.put("scp", Arrays.asList("message:read", "message:write"));
claims.put("scope", "missive:read missive:write");
Expand All @@ -102,25 +101,13 @@ public void convertWhenTokenHasBothScopeAndScpThenScopeAttributeIsTranslatedToAu

Collection<GrantedAuthority> authorities = authentication.getAuthorities();

assertThat(authorities).containsExactly(
assertThat(authorities).containsExactlyInAnyOrder(
new SimpleGrantedAuthority("SCOPE_message:read"),
new SimpleGrantedAuthority("SCOPE_message:write"),
new SimpleGrantedAuthority("SCOPE_missive:read"),
new SimpleGrantedAuthority("SCOPE_missive:write"));
}

@Test
public void convertWhenTokenHasEmptyScopeAndNonEmptyScpThenScopeAttributeIsTranslatedToNoAuthorities() {
Map<String, Object> claims = new HashMap<>();
claims.put("scp", Arrays.asList("message:read", "message:write"));
claims.put("scope", "");
Jwt jwt = this.jwt(claims);

AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt).block();

Collection<GrantedAuthority> authorities = authentication.getAuthorities();

assertThat(authorities).containsExactly();
}

private Jwt jwt(Map<String, Object> claims) {
Map<String, Object> headers = new HashMap<>();
headers.put("alg", JwsAlgorithms.RS256);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,15 @@
import java.util.Map;

import org.junit.Test;

import reactor.core.publisher.Flux;

import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
import org.springframework.security.oauth2.jwt.Jwt;

import reactor.core.publisher.Flux;

/**
* Tests for {@link ReactiveJwtAuthenticationConverter}
*
Expand All @@ -52,7 +51,7 @@ public void convertWhenDefaultGrantedAuthoritiesConverterSet() {
AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt).block();
Collection<GrantedAuthority> authorities = authentication.getAuthorities();

assertThat(authorities).containsExactly(
assertThat(authorities).containsExactlyInAnyOrder(
new SimpleGrantedAuthority("SCOPE_message:read"),
new SimpleGrantedAuthority("SCOPE_message:write"));
}
Expand All @@ -76,7 +75,7 @@ public void convertWithOverriddenGrantedAuthoritiesConverter() {
AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt).block();
Collection<GrantedAuthority> authorities = authentication.getAuthorities();

assertThat(authorities).containsExactly(
assertThat(authorities).containsExactlyInAnyOrder(
new SimpleGrantedAuthority("blah"));
}

Expand Down
Loading