From 0f7dff377452004d119f9d58900895638503fb5f Mon Sep 17 00:00:00 2001 From: Eric Deandrea Date: Wed, 12 Dec 2018 08:11:12 -0500 Subject: [PATCH] Introduce ReactiveJwtAuthenticationConverter Some changes based on PR comments Fixes gh-6273 --- .../JwtAuthenticationConverter.java | 56 ++++----- .../JwtGrantedAuthoritiesConverter.java | 78 ++++++++++++ .../ReactiveJwtAuthenticationConverter.java | 57 +++++++++ ...JwtGrantedAuthoritiesConverterAdapter.java | 52 ++++++++ .../JwtAuthenticationConverterTests.java | 75 +++-------- .../JwtGrantedAuthoritiesConverterTests.java | 117 ++++++++++++++++++ ...activeJwtAuthenticationConverterTests.java | 89 +++++++++++++ ...antedAuthoritiesConverterAdapterTests.java | 75 +++++++++++ 8 files changed, 511 insertions(+), 88 deletions(-) create mode 100644 oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtGrantedAuthoritiesConverter.java create mode 100644 oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtAuthenticationConverter.java create mode 100644 oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtGrantedAuthoritiesConverterAdapter.java create mode 100644 oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtGrantedAuthoritiesConverterTests.java create mode 100644 oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtAuthenticationConverterTests.java create mode 100644 oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtGrantedAuthoritiesConverterAdapterTests.java diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverter.java index 0daae1e0c6..f289bbc40d 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverter.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverter.java @@ -16,17 +16,13 @@ package org.springframework.security.oauth2.server.resource.authentication; -import java.util.Arrays; import java.util.Collection; -import java.util.Collections; -import java.util.stream.Collectors; 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.jwt.Jwt; -import org.springframework.util.StringUtils; +import org.springframework.util.Assert; /** * @author Rob Winch @@ -34,39 +30,39 @@ import org.springframework.util.StringUtils; * @since 5.1 */ public class JwtAuthenticationConverter implements Converter { - private static final String SCOPE_AUTHORITY_PREFIX = "SCOPE_"; - - private static final Collection WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES = - Arrays.asList("scope", "scp"); - + private Converter> jwtGrantedAuthoritiesConverter + = new JwtGrantedAuthoritiesConverter(); + @Override public final AbstractAuthenticationToken convert(Jwt jwt) { Collection authorities = extractAuthorities(jwt); return new JwtAuthenticationToken(jwt, authorities); } + /** + * Extracts the {@link GrantedAuthority}s from scope attributes typically found in a {@link Jwt} + * + * @param jwt The token + * @return The collection of {@link GrantedAuthority}s found on the token + * @deprecated Since 5.2. Use your own custom converter instead + * @see JwtGrantedAuthoritiesConverter + * @see #setJwtGrantedAuthoritiesConverter(Converter) + */ + @Deprecated protected Collection extractAuthorities(Jwt jwt) { - return this.getScopes(jwt) - .stream() - .map(authority -> SCOPE_AUTHORITY_PREFIX + authority) - .map(SimpleGrantedAuthority::new) - .collect(Collectors.toList()); + return this.jwtGrantedAuthoritiesConverter.convert(jwt); } - 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; - } - } - - return Collections.emptyList(); + /** + * Sets the {@link Converter Converter<Jwt, Collection<GrantedAuthority>>} to use. + * Defaults to {@link JwtGrantedAuthoritiesConverter}. + * + * @param jwtGrantedAuthoritiesConverter The converter + * @since 5.2 + * @see JwtGrantedAuthoritiesConverter + */ + public void setJwtGrantedAuthoritiesConverter(Converter> jwtGrantedAuthoritiesConverter) { + Assert.notNull(jwtGrantedAuthoritiesConverter, "jwtGrantedAuthoritiesConverter cannot be null"); + this.jwtGrantedAuthoritiesConverter = jwtGrantedAuthoritiesConverter; } } 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 new file mode 100644 index 0000000000..03a5a9b339 --- /dev/null +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtGrantedAuthoritiesConverter.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.server.resource.authentication; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.stream.Collectors; + +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; + +/** + * Extracts the {@link GrantedAuthority}s from scope attributes typically found in a + * {@link Jwt}. + * + * @author Eric Deandrea + * @since 5.2 + */ +public final class JwtGrantedAuthoritiesConverter implements Converter> { + private static final String SCOPE_AUTHORITY_PREFIX = "SCOPE_"; + + private static final Collection WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES = + Arrays.asList("scope", "scp"); + + /** + * Extracts the authorities + * @param jwt The {@link Jwt} token + * @return The {@link GrantedAuthority authorities} read from the token scopes + */ + @Override + public Collection convert(Jwt jwt) { + return getScopes(jwt) + .stream() + .map(authority -> SCOPE_AUTHORITY_PREFIX + authority) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + } + + /** + * 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; + } + } + + return Collections.emptyList(); + } +} diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtAuthenticationConverter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtAuthenticationConverter.java new file mode 100644 index 0000000000..8db803bb4f --- /dev/null +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtAuthenticationConverter.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.server.resource.authentication; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.util.Assert; + +/** + * Reactive version of {@link JwtAuthenticationConverter} for converting a {@link Jwt} + * to a {@link AbstractAuthenticationToken Mono<AbstractAuthenticationToken>}. + * + * @author Eric Deandrea + * @since 5.2 + */ +public final class ReactiveJwtAuthenticationConverter implements Converter> { + private Converter> jwtGrantedAuthoritiesConverter + = new ReactiveJwtGrantedAuthoritiesConverterAdapter(new JwtGrantedAuthoritiesConverter()); + + @Override + public Mono convert(Jwt jwt) { + return this.jwtGrantedAuthoritiesConverter.convert(jwt) + .collectList() + .map(authorities -> new JwtAuthenticationToken(jwt, authorities)); + } + + /** + * Sets the {@link Converter Converter<Jwt, Flux<GrantedAuthority>>} to use. + * Defaults to a reactive {@link JwtGrantedAuthoritiesConverter}. + * + * @param jwtGrantedAuthoritiesConverter The converter + * @see JwtGrantedAuthoritiesConverter + */ + public void setJwtGrantedAuthoritiesConverter(Converter> jwtGrantedAuthoritiesConverter) { + Assert.notNull(jwtGrantedAuthoritiesConverter, "jwtGrantedAuthoritiesConverter cannot be null"); + this.jwtGrantedAuthoritiesConverter = jwtGrantedAuthoritiesConverter; + } +} diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtGrantedAuthoritiesConverterAdapter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtGrantedAuthoritiesConverterAdapter.java new file mode 100644 index 0000000000..506d7470aa --- /dev/null +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtGrantedAuthoritiesConverterAdapter.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.server.resource.authentication; + +import java.util.Collection; + +import reactor.core.publisher.Flux; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.util.Assert; + +/** + * Adapts a {@link Converter Converter<Jwt, Collection<GrantedAuthority>>} to a + * {@link Converter Converter<Jwt, Flux<GrantedAuthority>>}. + *

+ * Make sure the {@link Converter Converter<Jwt, Collection<GrantedAuthority>>} + * being adapted is non-blocking. + *

+ * + * @author Eric Deandrea + * @since 5.2 + * @see JwtGrantedAuthoritiesConverter + */ +public final class ReactiveJwtGrantedAuthoritiesConverterAdapter implements Converter> { + private final Converter> grantedAuthoritiesConverter; + + public ReactiveJwtGrantedAuthoritiesConverterAdapter(Converter> grantedAuthoritiesConverter) { + Assert.notNull(grantedAuthoritiesConverter, "grantedAuthoritiesConverter cannot be null"); + this.grantedAuthoritiesConverter = grantedAuthoritiesConverter; + } + + @Override + public Flux convert(Jwt jwt) { + return Flux.fromIterable(this.grantedAuthoritiesConverter.convert(jwt)); + } +} 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 5c64974a54..4738975ba9 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 @@ -16,6 +16,9 @@ package org.springframework.security.oauth2.server.resource.authentication; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + import java.time.Instant; import java.util.Arrays; import java.util.Collection; @@ -23,17 +26,15 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import org.assertj.core.util.Maps; 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 JwtAuthenticationConverter} * @@ -43,7 +44,7 @@ public class JwtAuthenticationConverterTests { JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); @Test - public void convertWhenTokenHasScopeAttributeThenTranslatedToAuthorities() { + public void convertWhenDefaultGrantedAuthoritiesConverterSet() { Jwt jwt = this.jwt(Collections.singletonMap("scope", "message:read message:write")); AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt); @@ -55,68 +56,26 @@ public class JwtAuthenticationConverterTests { } @Test - public void convertWhenTokenHasEmptyScopeAttributeThenTranslatedToNoAuthorities() { - Jwt jwt = this.jwt(Collections.singletonMap("scope", "")); - - AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt); - - Collection authorities = authentication.getAuthorities(); - - assertThat(authorities).containsExactly(); + public void whenSettingNullGrantedAuthoritiesConverter() { + assertThatIllegalArgumentException() + .isThrownBy(() -> this.jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(null)) + .withMessage("jwtGrantedAuthoritiesConverter cannot be null"); } @Test - public void convertWhenTokenHasScpAttributeThenTranslatedToAuthorities() { - Jwt jwt = this.jwt(Collections.singletonMap("scp", Arrays.asList("message:read", "message:write"))); + public void convertWithOverriddenGrantedAuthoritiesConverter() { + Jwt jwt = this.jwt(Collections.singletonMap("scope", "message:read message:write")); + + Converter> grantedAuthoritiesConverter = + token -> Arrays.asList(new SimpleGrantedAuthority("blah")); + + this.jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt); - Collection authorities = authentication.getAuthorities(); assertThat(authorities).containsExactly( - new SimpleGrantedAuthority("SCOPE_message:read"), - new SimpleGrantedAuthority("SCOPE_message:write")); - } - - @Test - public void convertWhenTokenHasEmptyScpAttributeThenTranslatedToNoAuthorities() { - Jwt jwt = this.jwt(Maps.newHashMap("scp", Arrays.asList())); - - AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt); - - Collection authorities = authentication.getAuthorities(); - - assertThat(authorities).containsExactly(); - } - - @Test - public void convertWhenTokenHasBothScopeAndScpThenScopeAttributeIsTranslatedToAuthorities() { - Map claims = new HashMap<>(); - claims.put("scp", Arrays.asList("message:read", "message:write")); - claims.put("scope", "missive:read missive:write"); - Jwt jwt = this.jwt(claims); - - AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt); - - Collection authorities = authentication.getAuthorities(); - - assertThat(authorities).containsExactly( - 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); - - Collection authorities = authentication.getAuthorities(); - - assertThat(authorities).containsExactly(); + new SimpleGrantedAuthority("blah")); } private Jwt jwt(Map claims) { 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 new file mode 100644 index 0000000000..13355bbd34 --- /dev/null +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtGrantedAuthoritiesConverterTests.java @@ -0,0 +1,117 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +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; +import org.springframework.security.oauth2.jwt.Jwt; + +/** + * Tests for {@link JwtGrantedAuthoritiesConverter} + * + * @author Eric Deandrea + * @since 5.2 + */ +public class JwtGrantedAuthoritiesConverterTests { + private JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); + + @Test + public void convertWhenTokenHasScopeAttributeThenTranslatedToAuthorities() { + Jwt jwt = this.jwt(Collections.singletonMap("scope", "message:read message:write")); + + Collection authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt); + + assertThat(authorities).containsExactly( + new SimpleGrantedAuthority("SCOPE_message:read"), + new SimpleGrantedAuthority("SCOPE_message:write")); + } + + @Test + public void convertWhenTokenHasEmptyScopeAttributeThenTranslatedToNoAuthorities() { + Jwt jwt = this.jwt(Collections.singletonMap("scope", "")); + + Collection authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt); + + assertThat(authorities).containsExactly(); + } + + @Test + public void convertWhenTokenHasScpAttributeThenTranslatedToAuthorities() { + Jwt jwt = this.jwt(Collections.singletonMap("scp", Arrays.asList("message:read", "message:write"))); + + Collection authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt); + + assertThat(authorities).containsExactly( + new SimpleGrantedAuthority("SCOPE_message:read"), + new SimpleGrantedAuthority("SCOPE_message:write")); + } + + @Test + public void convertWhenTokenHasEmptyScpAttributeThenTranslatedToNoAuthorities() { + Jwt jwt = this.jwt(Maps.newHashMap("scp", Arrays.asList())); + + Collection authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt); + + assertThat(authorities).containsExactly(); + } + + @Test + public void convertWhenTokenHasBothScopeAndScpThenScopeAttributeIsTranslatedToAuthorities() { + Map 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 authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt); + + assertThat(authorities).containsExactly( + 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); + + return new Jwt("token", Instant.now(), Instant.now().plusSeconds(3600), headers, claims); + } +} 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 new file mode 100644 index 0000000000..51176fa5ca --- /dev/null +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtAuthenticationConverterTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.server.resource.authentication; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +import java.time.Instant; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +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; + +/** + * Tests for {@link ReactiveJwtAuthenticationConverter} + * + * @author Eric Deandrea + * @since 5.2 + */ +public class ReactiveJwtAuthenticationConverterTests { + ReactiveJwtAuthenticationConverter jwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter(); + + @Test + public void convertWhenDefaultGrantedAuthoritiesConverterSet() { + Jwt jwt = this.jwt(Collections.singletonMap("scope", "message:read message:write")); + + AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt).block(); + Collection authorities = authentication.getAuthorities(); + + assertThat(authorities).containsExactly( + new SimpleGrantedAuthority("SCOPE_message:read"), + new SimpleGrantedAuthority("SCOPE_message:write")); + } + + @Test + public void whenSettingNullGrantedAuthoritiesConverter() { + assertThatIllegalArgumentException() + .isThrownBy(() -> this.jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(null)) + .withMessage("jwtGrantedAuthoritiesConverter cannot be null"); + } + + @Test + public void convertWithOverriddenGrantedAuthoritiesConverter() { + Jwt jwt = this.jwt(Collections.singletonMap("scope", "message:read message:write")); + + Converter> grantedAuthoritiesConverter = + token -> Flux.just(new SimpleGrantedAuthority("blah")); + + this.jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); + + AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt).block(); + Collection authorities = authentication.getAuthorities(); + + assertThat(authorities).containsExactly( + new SimpleGrantedAuthority("blah")); + } + + private Jwt jwt(Map claims) { + Map headers = new HashMap<>(); + headers.put("alg", JwsAlgorithms.RS256); + + return new Jwt("token", Instant.now(), Instant.now().plusSeconds(3600), headers, claims); + } +} 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 new file mode 100644 index 0000000000..66fc2f621d --- /dev/null +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtGrantedAuthoritiesConverterAdapterTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.server.resource.authentication; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +import java.time.Instant; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +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; +import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; +import org.springframework.security.oauth2.jwt.Jwt; + +/** + * Tests for {@link ReactiveJwtGrantedAuthoritiesConverterAdapter} + * + * @author Eric Deandrea + * @since 5.2 + */ +public class ReactiveJwtGrantedAuthoritiesConverterAdapterTests { + @Test + public void convertWithGrantedAuthoritiesConverter() { + Jwt jwt = this.jwt(Collections.singletonMap("scope", "message:read message:write")); + + Converter> grantedAuthoritiesConverter = + token -> Arrays.asList(new SimpleGrantedAuthority("blah")); + + Collection authorities = + new ReactiveJwtGrantedAuthoritiesConverterAdapter(grantedAuthoritiesConverter) + .convert(jwt) + .toStream() + .collect(Collectors.toList()); + + assertThat(authorities).containsExactly( + new SimpleGrantedAuthority("blah")); + } + + @Test + public void whenConstructingWithInvalidConverter() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new ReactiveJwtGrantedAuthoritiesConverterAdapter(null)) + .withMessage("grantedAuthoritiesConverter cannot be null"); + } + + private Jwt jwt(Map claims) { + Map headers = new HashMap<>(); + headers.put("alg", JwsAlgorithms.RS256); + + return new Jwt("token", Instant.now(), Instant.now().plusSeconds(3600), headers, claims); + } +}