From d24586c759d7551bdc516cee21e089a359897cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Wacongne?= Date: Sun, 14 Jul 2019 15:26:01 +0000 Subject: [PATCH] gh-7100 JwtGrantedAuthoritiesConverter: configurable authorities claim names --- .../JwtGrantedAuthoritiesConverter.java | 76 ++++++++++++------- .../JwtAuthenticationConverterTests.java | 29 ++++++- .../JwtGrantedAuthoritiesConverterTests.java | 30 +++----- ...wtAuthenticationConverterAdapterTests.java | 33 +++----- ...activeJwtAuthenticationConverterTests.java | 9 +-- ...antedAuthoritiesConverterAdapterTests.java | 3 +- 6 files changed, 100 insertions(+), 80 deletions(-) diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtGrantedAuthoritiesConverter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtGrantedAuthoritiesConverter.java index c2e49066b42..d6a5ed369e4 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtGrantedAuthoritiesConverter.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtGrantedAuthoritiesConverter.java @@ -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; @@ -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> { - private static final String SCOPE_AUTHORITY_PREFIX = "SCOPE_"; + private static final String DEFAULT_AUTHORITIES_PREFIX = "SCOPE_"; - private static final Collection WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES = - Arrays.asList("scope", "scp"); + private static final Set WELL_KNOWN_SCOPE_CLAIM_NAMES = + Stream.of("scope", "scp").collect(Collectors.toSet()); + + private Set authoritiesClaimNames; + + public JwtGrantedAuthoritiesConverter(Collection 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 authoritiesClaimNames) { + this.authoritiesClaimNames = authoritiesClaimNames.stream().collect(Collectors.toSet()); + return this; + } /** * Extracts the authorities @@ -47,32 +75,26 @@ public final class JwtGrantedAuthoritiesConverter implements Converter 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 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) scopes; + @SuppressWarnings("unchecked") + private Stream 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) authorities).stream(); } - return Collections.emptyList(); + return Stream.empty(); } } diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverterTests.java index 7f72e8d21a2..765fb20b3f2 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverterTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverterTests.java @@ -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; @@ -41,8 +40,14 @@ * @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")); @@ -50,7 +55,7 @@ public void convertWhenDefaultGrantedAuthoritiesConverterSet() { AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt); Collection authorities = authentication.getAuthorities(); - assertThat(authorities).containsExactly( + assertThat(authorities).containsExactlyInAnyOrder( new SimpleGrantedAuthority("SCOPE_message:read"), new SimpleGrantedAuthority("SCOPE_message:write")); } @@ -74,10 +79,28 @@ public void convertWithOverriddenGrantedAuthoritiesConverter() { AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt); Collection authorities = authentication.getAuthorities(); - assertThat(authorities).containsExactly( + assertThat(authorities).containsExactlyInAnyOrder( new SimpleGrantedAuthority("blah")); } + @Test + public void convertWhenTokenHasAttributesClaimsWithDifferentPrefixesThenPrefixesAreCorrectlyApplied( ) { + Map 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 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 claims) { Map headers = new HashMap<>(); headers.put("alg", JwsAlgorithms.RS256); diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtGrantedAuthoritiesConverterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtGrantedAuthoritiesConverterTests.java index 385ac278832..e93eb82ebce 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtGrantedAuthoritiesConverterTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtGrantedAuthoritiesConverterTests.java @@ -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; @@ -40,7 +39,8 @@ * @since 5.2 */ public class JwtGrantedAuthoritiesConverterTests { - private JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); + private JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = + new JwtGrantedAuthoritiesConverter().addAuthoritiesClaimName("authorities"); @Test public void convertWhenTokenHasScopeAttributeThenTranslatedToAuthorities() { @@ -48,7 +48,7 @@ public void convertWhenTokenHasScopeAttributeThenTranslatedToAuthorities() { Collection authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt); - assertThat(authorities).containsExactly( + assertThat(authorities).containsExactlyInAnyOrder( new SimpleGrantedAuthority("SCOPE_message:read"), new SimpleGrantedAuthority("SCOPE_message:write")); } @@ -59,7 +59,7 @@ public void convertWhenTokenHasEmptyScopeAttributeThenTranslatedToNoAuthorities( Collection authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt); - assertThat(authorities).containsExactly(); + assertThat(authorities).containsExactlyInAnyOrder(); } @Test @@ -68,7 +68,7 @@ public void convertWhenTokenHasScpAttributeThenTranslatedToAuthorities() { Collection authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt); - assertThat(authorities).containsExactly( + assertThat(authorities).containsExactlyInAnyOrder( new SimpleGrantedAuthority("SCOPE_message:read"), new SimpleGrantedAuthority("SCOPE_message:write")); } @@ -79,11 +79,11 @@ public void convertWhenTokenHasEmptyScpAttributeThenTranslatedToNoAuthorities() Collection authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt); - assertThat(authorities).containsExactly(); + assertThat(authorities).containsExactlyInAnyOrder(); } @Test - public void convertWhenTokenHasBothScopeAndScpThenScopeAttributeIsTranslatedToAuthorities() { + public void convertWhenTokenHasBothScopeAndScpThenBothAttributesAreTranslatedToAuthorities() { Map claims = new HashMap<>(); claims.put("scp", Arrays.asList("message:read", "message:write")); claims.put("scope", "missive:read missive:write"); @@ -91,23 +91,13 @@ public void convertWhenTokenHasBothScopeAndScpThenScopeAttributeIsTranslatedToAu Collection 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 claims = new HashMap<>(); - claims.put("scp", Arrays.asList("message:read", "message:write")); - claims.put("scope", ""); - Jwt jwt = this.jwt(claims); - - Collection authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt); - - assertThat(authorities).containsExactly(); - } - private Jwt jwt(Map claims) { Map headers = new HashMap<>(); headers.put("alg", JwsAlgorithms.RS256); diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtAuthenticationConverterAdapterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtAuthenticationConverterAdapterTests.java index 6eeaba6257a..c4218ab3d6b 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtAuthenticationConverterAdapterTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtAuthenticationConverterAdapterTests.java @@ -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; @@ -24,7 +26,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; @@ -32,8 +33,6 @@ 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} * @@ -51,7 +50,7 @@ public void convertWhenTokenHasScopeAttributeThenTranslatedToAuthorities() { AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt).block(); Collection authorities = authentication.getAuthorities(); - assertThat(authorities).containsExactly( + assertThat(authorities).containsExactlyInAnyOrder( new SimpleGrantedAuthority("SCOPE_message:read"), new SimpleGrantedAuthority("SCOPE_message:write")); } @@ -64,7 +63,7 @@ public void convertWhenTokenHasEmptyScopeAttributeThenTranslatedToNoAuthorities( Collection authorities = authentication.getAuthorities(); - assertThat(authorities).containsExactly(); + assertThat(authorities).containsExactlyInAnyOrder(); } @Test @@ -75,7 +74,7 @@ public void convertWhenTokenHasScpAttributeThenTranslatedToAuthorities() { Collection authorities = authentication.getAuthorities(); - assertThat(authorities).containsExactly( + assertThat(authorities).containsExactlyInAnyOrder( new SimpleGrantedAuthority("SCOPE_message:read"), new SimpleGrantedAuthority("SCOPE_message:write")); } @@ -88,11 +87,11 @@ public void convertWhenTokenHasEmptyScpAttributeThenTranslatedToNoAuthorities() Collection authorities = authentication.getAuthorities(); - assertThat(authorities).containsExactly(); + assertThat(authorities).containsExactlyInAnyOrder(); } @Test - public void convertWhenTokenHasBothScopeAndScpThenScopeAttributeIsTranslatedToAuthorities() { + public void convertWhenTokenHasBothScopeAndScpThenBothAttributesAreTranslatedToAuthorities() { Map claims = new HashMap<>(); claims.put("scp", Arrays.asList("message:read", "message:write")); claims.put("scope", "missive:read missive:write"); @@ -102,25 +101,13 @@ public void convertWhenTokenHasBothScopeAndScpThenScopeAttributeIsTranslatedToAu Collection 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 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 authorities = authentication.getAuthorities(); - - assertThat(authorities).containsExactly(); - } - private Jwt jwt(Map claims) { Map headers = new HashMap<>(); headers.put("alg", JwsAlgorithms.RS256); diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtAuthenticationConverterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtAuthenticationConverterTests.java index 67bcc56d28f..1bb78ffb5e6 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtAuthenticationConverterTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtAuthenticationConverterTests.java @@ -26,9 +26,6 @@ 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; @@ -36,6 +33,8 @@ import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; import org.springframework.security.oauth2.jwt.Jwt; +import reactor.core.publisher.Flux; + /** * Tests for {@link ReactiveJwtAuthenticationConverter} * @@ -52,7 +51,7 @@ public void convertWhenDefaultGrantedAuthoritiesConverterSet() { AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt).block(); Collection authorities = authentication.getAuthorities(); - assertThat(authorities).containsExactly( + assertThat(authorities).containsExactlyInAnyOrder( new SimpleGrantedAuthority("SCOPE_message:read"), new SimpleGrantedAuthority("SCOPE_message:write")); } @@ -76,7 +75,7 @@ public void convertWithOverriddenGrantedAuthoritiesConverter() { AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt).block(); Collection authorities = authentication.getAuthorities(); - assertThat(authorities).containsExactly( + assertThat(authorities).containsExactlyInAnyOrder( new SimpleGrantedAuthority("blah")); } diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtGrantedAuthoritiesConverterAdapterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtGrantedAuthoritiesConverterAdapterTests.java index 378a2ce55ac..17d8b7eac4b 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtGrantedAuthoritiesConverterAdapterTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtGrantedAuthoritiesConverterAdapterTests.java @@ -28,7 +28,6 @@ import java.util.stream.Collectors; import org.junit.Test; - import org.springframework.core.convert.converter.Converter; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -55,7 +54,7 @@ public void convertWithGrantedAuthoritiesConverter() { .toStream() .collect(Collectors.toList()); - assertThat(authorities).containsExactly( + assertThat(authorities).containsExactlyInAnyOrder( new SimpleGrantedAuthority("blah")); }