浏览代码

Introduce ReactiveJwtAuthenticationConverter

Some changes based on PR comments

Fixes gh-6273
Eric Deandrea 6 年之前
父节点
当前提交
0f7dff3774

+ 26 - 30
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<Jwt, AbstractAuthenticationToken> {
-	private static final String SCOPE_AUTHORITY_PREFIX = "SCOPE_";
-
-	private static final Collection<String> WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES =
-			Arrays.asList("scope", "scp");
-
+	private Converter<Jwt, Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter
+			= new JwtGrantedAuthoritiesConverter();
 
+	@Override
 	public final AbstractAuthenticationToken convert(Jwt jwt) {
 		Collection<GrantedAuthority> 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<GrantedAuthority> 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<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;
-			}
-		}
-
-		return Collections.emptyList();
+	/**
+	 * Sets the {@link Converter Converter&lt;Jwt, Collection&lt;GrantedAuthority&gt;&gt;} to use.
+	 * Defaults to {@link JwtGrantedAuthoritiesConverter}.
+	 *
+	 * @param jwtGrantedAuthoritiesConverter The converter
+	 * @since 5.2
+	 * @see JwtGrantedAuthoritiesConverter
+	 */
+	public void setJwtGrantedAuthoritiesConverter(Converter<Jwt, Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter) {
+		Assert.notNull(jwtGrantedAuthoritiesConverter, "jwtGrantedAuthoritiesConverter cannot be null");
+		this.jwtGrantedAuthoritiesConverter = jwtGrantedAuthoritiesConverter;
 	}
 }

+ 78 - 0
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<Jwt, Collection<GrantedAuthority>> {
+	private static final String SCOPE_AUTHORITY_PREFIX = "SCOPE_";
+
+	private static final Collection<String> 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<GrantedAuthority> 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<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;
+			}
+		}
+
+		return Collections.emptyList();
+	}
+}

+ 57 - 0
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&lt;AbstractAuthenticationToken&gt;}.
+ *
+ * @author Eric Deandrea
+ * @since 5.2
+ */
+public final class ReactiveJwtAuthenticationConverter implements Converter<Jwt, Mono<AbstractAuthenticationToken>> {
+	private Converter<Jwt, Flux<GrantedAuthority>> jwtGrantedAuthoritiesConverter
+			= new ReactiveJwtGrantedAuthoritiesConverterAdapter(new JwtGrantedAuthoritiesConverter());
+
+	@Override
+	public Mono<AbstractAuthenticationToken> convert(Jwt jwt) {
+		return this.jwtGrantedAuthoritiesConverter.convert(jwt)
+				.collectList()
+				.map(authorities -> new JwtAuthenticationToken(jwt, authorities));
+	}
+
+	/**
+	 * Sets the {@link Converter Converter&lt;Jwt, Flux&lt;GrantedAuthority&gt;&gt;} to use.
+	 * Defaults to a reactive {@link JwtGrantedAuthoritiesConverter}.
+	 *
+	 * @param jwtGrantedAuthoritiesConverter The converter
+	 * @see JwtGrantedAuthoritiesConverter
+	 */
+	public void setJwtGrantedAuthoritiesConverter(Converter<Jwt, Flux<GrantedAuthority>> jwtGrantedAuthoritiesConverter) {
+		Assert.notNull(jwtGrantedAuthoritiesConverter, "jwtGrantedAuthoritiesConverter cannot be null");
+		this.jwtGrantedAuthoritiesConverter = jwtGrantedAuthoritiesConverter;
+	}
+}

+ 52 - 0
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&lt;Jwt, Collection&lt;GrantedAuthority&gt;&gt;} to a
+ * {@link Converter Converter&lt;Jwt, Flux&lt;GrantedAuthority&gt;&gt;}.
+ * <p>
+ *   Make sure the {@link Converter Converter&lt;Jwt, Collection&lt;GrantedAuthority&gt;&gt;}
+ *   being adapted is non-blocking.
+ * </p>
+ *
+ * @author Eric Deandrea
+ * @since 5.2
+ * @see JwtGrantedAuthoritiesConverter
+ */
+public final class ReactiveJwtGrantedAuthoritiesConverterAdapter implements Converter<Jwt, Flux<GrantedAuthority>> {
+	private final Converter<Jwt, Collection<GrantedAuthority>> grantedAuthoritiesConverter;
+
+	public ReactiveJwtGrantedAuthoritiesConverterAdapter(Converter<Jwt, Collection<GrantedAuthority>> grantedAuthoritiesConverter) {
+		Assert.notNull(grantedAuthoritiesConverter, "grantedAuthoritiesConverter cannot be null");
+		this.grantedAuthoritiesConverter = grantedAuthoritiesConverter;
+	}
+
+	@Override
+	public Flux<GrantedAuthority> convert(Jwt jwt) {
+		return Flux.fromIterable(this.grantedAuthoritiesConverter.convert(jwt));
+	}
+}

+ 15 - 56
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<GrantedAuthority> authorities = authentication.getAuthorities();
-
-		assertThat(authorities).containsExactly();
-	}
-
-	@Test
-	public void convertWhenTokenHasScpAttributeThenTranslatedToAuthorities() {
-		Jwt jwt = this.jwt(Collections.singletonMap("scp", Arrays.asList("message:read", "message:write")));
-
-		AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt);
-
-		Collection<GrantedAuthority> authorities = authentication.getAuthorities();
-
-		assertThat(authorities).containsExactly(
-				new SimpleGrantedAuthority("SCOPE_message:read"),
-				new SimpleGrantedAuthority("SCOPE_message:write"));
+	public void whenSettingNullGrantedAuthoritiesConverter() {
+		assertThatIllegalArgumentException()
+				.isThrownBy(() -> this.jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(null))
+				.withMessage("jwtGrantedAuthoritiesConverter cannot be null");
 	}
 
 	@Test
-	public void convertWhenTokenHasEmptyScpAttributeThenTranslatedToNoAuthorities() {
-		Jwt jwt = this.jwt(Maps.newHashMap("scp", Arrays.asList()));
-
-		AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt);
-
-		Collection<GrantedAuthority> authorities = authentication.getAuthorities();
+	public void convertWithOverriddenGrantedAuthoritiesConverter() {
+		Jwt jwt = this.jwt(Collections.singletonMap("scope", "message:read message:write"));
 
-		assertThat(authorities).containsExactly();
-	}
+		Converter<Jwt, Collection<GrantedAuthority>> grantedAuthoritiesConverter =
+				token -> Arrays.asList(new SimpleGrantedAuthority("blah"));
 
-	@Test
-	public void convertWhenTokenHasBothScopeAndScpThenScopeAttributeIsTranslatedToAuthorities() {
-		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);
+		this.jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
 
 		AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt);
-
 		Collection<GrantedAuthority> authorities = authentication.getAuthorities();
 
 		assertThat(authorities).containsExactly(
-				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);
-
-		Collection<GrantedAuthority> authorities = authentication.getAuthorities();
-
-		assertThat(authorities).containsExactly();
+				new SimpleGrantedAuthority("blah"));
 	}
 
 	private Jwt jwt(Map<String, Object> claims) {

+ 117 - 0
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<GrantedAuthority> 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<GrantedAuthority> 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<GrantedAuthority> 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<GrantedAuthority> authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt);
+
+		assertThat(authorities).containsExactly();
+	}
+
+	@Test
+	public void convertWhenTokenHasBothScopeAndScpThenScopeAttributeIsTranslatedToAuthorities() {
+		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(
+				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);
+
+		return new Jwt("token", Instant.now(), Instant.now().plusSeconds(3600), headers, claims);
+	}
+}

+ 89 - 0
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<GrantedAuthority> 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<Jwt, Flux<GrantedAuthority>> grantedAuthoritiesConverter =
+				token -> Flux.just(new SimpleGrantedAuthority("blah"));
+
+		this.jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
+
+		AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt).block();
+		Collection<GrantedAuthority> authorities = authentication.getAuthorities();
+
+		assertThat(authorities).containsExactly(
+				new SimpleGrantedAuthority("blah"));
+	}
+
+	private Jwt jwt(Map<String, Object> claims) {
+		Map<String, Object> headers = new HashMap<>();
+		headers.put("alg", JwsAlgorithms.RS256);
+
+		return new Jwt("token", Instant.now(), Instant.now().plusSeconds(3600), headers, claims);
+	}
+}

+ 75 - 0
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<Jwt, Collection<GrantedAuthority>> grantedAuthoritiesConverter =
+				token -> Arrays.asList(new SimpleGrantedAuthority("blah"));
+
+		Collection<GrantedAuthority> 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<String, Object> claims) {
+		Map<String, Object> headers = new HashMap<>();
+		headers.put("alg", JwsAlgorithms.RS256);
+
+		return new Jwt("token", Instant.now(), Instant.now().plusSeconds(3600), headers, claims);
+	}
+}