Quellcode durchsuchen

Polish OAuth2AuthorizationManagers

- Add OAuth2ReactiveAuthorizationManagers
- Code to interfaces
- Align error message with the same in
AuthorityAuthorizationManager
- Adjust expectations in tests to confirm an
appropriately constructed authorizaion manager
- Add JavaDoc and reference documentation

Issue gh-13654
Josh Cummings vor 1 Jahr
Ursprung
Commit
a3227f041c

+ 14 - 6
docs/modules/ROOT/pages/reactive/oauth2/resource-server/jwt.adoc

@@ -165,11 +165,13 @@ Java::
 +
 [source,java,role="primary"]
 ----
+import static org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope;
+
 @Bean
 SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
 	http
 		.authorizeExchange(exchanges -> exchanges
-			.pathMatchers("/message/**").hasAuthority("SCOPE_message:read")
+			.pathMatchers("/message/**").access(hasScope("message:read"))
 			.anyExchange().authenticated()
 		)
 		.oauth2ResourceServer(oauth2 -> oauth2
@@ -183,11 +185,13 @@ Kotlin::
 +
 [source,kotlin,role="secondary"]
 ----
+import org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope
+
 @Bean
 fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
     return http {
         authorizeExchange {
-            authorize("/message/**", hasAuthority("SCOPE_message:read"))
+            authorize("/message/**", hasScope("message:read"))
             authorize(anyExchange, authenticated)
         }
         oauth2ResourceServer {
@@ -682,12 +686,14 @@ Java::
 +
 [source,java,role="primary"]
 ----
+import static org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope;
+
 @Bean
 SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
 	http
 		.authorizeExchange(exchanges -> exchanges
-			.mvcMatchers("/contacts/**").hasAuthority("SCOPE_contacts")
-			.mvcMatchers("/messages/**").hasAuthority("SCOPE_messages")
+			.mvcMatchers("/contacts/**").access(hasScope("contacts"))
+			.mvcMatchers("/messages/**").access(hasScope("messages"))
 			.anyExchange().authenticated()
 		)
 		.oauth2ResourceServer(OAuth2ResourceServerSpec::jwt);
@@ -699,12 +705,14 @@ Kotlin::
 +
 [source,kotlin,role="secondary"]
 ----
+import org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope
+
 @Bean
 fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
     return http {
         authorizeExchange {
-            authorize("/contacts/**", hasAuthority("SCOPE_contacts"))
-            authorize("/messages/**", hasAuthority("SCOPE_messages"))
+            authorize("/contacts/**", hasScope("contacts"))
+            authorize("/messages/**", hasScope("messages"))
             authorize(anyExchange, authenticated)
         }
         oauth2ResourceServer {

+ 14 - 6
docs/modules/ROOT/pages/reactive/oauth2/resource-server/opaque-token.adoc

@@ -214,6 +214,8 @@ Java::
 +
 [source,java,role="primary"]
 ----
+import static org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope;
+
 @Configuration
 @EnableWebFluxSecurity
 public class MyCustomSecurityConfiguration {
@@ -221,7 +223,7 @@ public class MyCustomSecurityConfiguration {
     SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
         http
             .authorizeExchange(exchanges -> exchanges
-                .pathMatchers("/messages/**").hasAuthority("SCOPE_message:read")
+                .pathMatchers("/messages/**").access(hasScope("message:read"))
                 .anyExchange().authenticated()
             )
             .oauth2ResourceServer(oauth2 -> oauth2
@@ -238,11 +240,13 @@ Kotlin::
 +
 [source,kotlin,role="secondary"]
 ----
+import org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope
+
 @Bean
 fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
     return http {
         authorizeExchange {
-            authorize("/messages/**", hasAuthority("SCOPE_message:read"))
+            authorize("/messages/**", hasScope("message:read"))
             authorize(anyExchange, authenticated)
         }
         oauth2ResourceServer {
@@ -442,6 +446,8 @@ Java::
 +
 [source,java,role="primary"]
 ----
+import static org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope;
+
 @Configuration
 @EnableWebFluxSecurity
 public class MappedAuthorities {
@@ -449,8 +455,8 @@ public class MappedAuthorities {
     SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
         http
             .authorizeExchange(exchange -> exchange
-                .pathMatchers("/contacts/**").hasAuthority("SCOPE_contacts")
-                .pathMatchers("/messages/**").hasAuthority("SCOPE_messages")
+                .pathMatchers("/contacts/**").access(hasScope("contacts"))
+                .pathMatchers("/messages/**").access(hasScope("messages"))
                 .anyExchange().authenticated()
             )
             .oauth2ResourceServer(ServerHttpSecurity.OAuth2ResourceServerSpec::opaqueToken);
@@ -463,12 +469,14 @@ Kotlin::
 +
 [source,kotlin,role="secondary"]
 ----
+import org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope
+
 @Bean
 fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
     return http {
         authorizeExchange {
-            authorize("/contacts/**", hasAuthority("SCOPE_contacts"))
-            authorize("/messages/**", hasAuthority("SCOPE_messages"))
+            authorize("/contacts/**", hasScope("contacts"))
+            authorize("/messages/**", hasScope("messages"))
             authorize(anyExchange, authenticated)
         }
         oauth2ResourceServer {

+ 14 - 6
docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc

@@ -211,6 +211,8 @@ Java::
 +
 [source,java,role="primary"]
 ----
+import static org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope;
+
 @Configuration
 @EnableWebSecurity
 public class MyCustomSecurityConfiguration {
@@ -218,7 +220,7 @@ public class MyCustomSecurityConfiguration {
     public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
         http
             .authorizeHttpRequests(authorize -> authorize
-                .requestMatchers("/messages/**").hasAuthority("SCOPE_message:read")
+                .requestMatchers("/messages/**").access(hasScope("message:read"))
                 .anyRequest().authenticated()
             )
             .oauth2ResourceServer(oauth2 -> oauth2
@@ -235,6 +237,8 @@ Kotlin::
 +
 [source,kotlin,role="secondary"]
 ----
+import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope
+
 @Configuration
 @EnableWebSecurity
 class MyCustomSecurityConfiguration {
@@ -242,7 +246,7 @@ class MyCustomSecurityConfiguration {
     open fun filterChain(http: HttpSecurity): SecurityFilterChain {
         http {
             authorizeRequests {
-                authorize("/messages/**", hasAuthority("SCOPE_message:read"))
+                authorize("/messages/**", hasScope("message:read"))
                 authorize(anyRequest, authenticated)
             }
             oauth2ResourceServer {
@@ -862,6 +866,8 @@ Java::
 +
 [source,java,role="primary"]
 ----
+import static org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope;
+
 @Configuration
 @EnableWebSecurity
 public class DirectlyConfiguredJwkSetUri {
@@ -869,8 +875,8 @@ public class DirectlyConfiguredJwkSetUri {
     public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
         http
             .authorizeHttpRequests(authorize -> authorize
-                .requestMatchers("/contacts/**").hasAuthority("SCOPE_contacts")
-                .requestMatchers("/messages/**").hasAuthority("SCOPE_messages")
+                .requestMatchers("/contacts/**").access(hasScope("contacts"))
+                .requestMatchers("/messages/**").access(hasScope("messages"))
                 .anyRequest().authenticated()
             )
             .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
@@ -883,6 +889,8 @@ Kotlin::
 +
 [source,kotlin,role="secondary"]
 ----
+import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope;
+
 @Configuration
 @EnableWebSecurity
 class DirectlyConfiguredJwkSetUri {
@@ -890,8 +898,8 @@ class DirectlyConfiguredJwkSetUri {
     open fun filterChain(http: HttpSecurity): SecurityFilterChain {
         http {
             authorizeRequests {
-                authorize("/contacts/**", hasAuthority("SCOPE_contacts"))
-                authorize("/messages/**", hasAuthority("SCOPE_messages"))
+                authorize("/contacts/**", hasScope("contacts"))
+                authorize("/messages/**", hasScope("messages"))
                 authorize(anyRequest, authenticated)
             }
             oauth2ResourceServer {

+ 14 - 6
docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc

@@ -239,6 +239,8 @@ Java::
 +
 [source,java,role="primary"]
 ----
+import static org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope;
+
 @Configuration
 @EnableWebSecurity
 public class MyCustomSecurityConfiguration {
@@ -246,7 +248,7 @@ public class MyCustomSecurityConfiguration {
     public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
         http
             .authorizeHttpRequests(authorize -> authorize
-                .requestMatchers("/messages/**").hasAuthority("SCOPE_message:read")
+                .requestMatchers("/messages/**").access(hasScope("message:read"))
                 .anyRequest().authenticated()
             )
             .oauth2ResourceServer(oauth2 -> oauth2
@@ -263,6 +265,8 @@ Kotlin::
 +
 [source,kotlin,role="secondary"]
 ----
+import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope;
+
 @Configuration
 @EnableWebSecurity
 class MyCustomSecurityConfiguration {
@@ -270,7 +274,7 @@ class MyCustomSecurityConfiguration {
     open fun filterChain(http: HttpSecurity): SecurityFilterChain {
         http {
             authorizeRequests {
-                authorize("/messages/**", hasAuthority("SCOPE_message:read"))
+                authorize("/messages/**", hasScope("SCOPE_message:read"))
                 authorize(anyRequest, authenticated)
             }
             oauth2ResourceServer {
@@ -547,6 +551,8 @@ Java::
 +
 [source,java,role="primary"]
 ----
+import static org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope;
+
 @Configuration
 @EnableWebSecurity
 public class MappedAuthorities {
@@ -554,8 +560,8 @@ public class MappedAuthorities {
     public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
         http
             .authorizeHttpRequests(authorizeRequests -> authorizeRequests
-                .requestMatchers("/contacts/**").hasAuthority("SCOPE_contacts")
-                .requestMatchers("/messages/**").hasAuthority("SCOPE_messages")
+                .requestMatchers("/contacts/**").access(hasScope("contacts"))
+                .requestMatchers("/messages/**").access(hasScope("messages"))
                 .anyRequest().authenticated()
             )
             .oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken);
@@ -568,6 +574,8 @@ Kotlin::
 +
 [source,kotlin,role="secondary"]
 ----
+import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope
+
 @Configuration
 @EnableWebSecurity
 class MappedAuthorities {
@@ -575,8 +583,8 @@ class MappedAuthorities {
     open fun filterChain(http: HttpSecurity): SecurityFilterChain {
        http {
             authorizeRequests {
-                authorize("/contacts/**", hasAuthority("SCOPE_contacts"))
-                authorize("/messages/**", hasAuthority("SCOPE_messages"))
+                authorize("/contacts/**", hasScope("contacts"))
+                authorize("/messages/**", hasScope("messages"))
                 authorize(anyRequest, authenticated)
             }
            oauth2ResourceServer {

+ 0 - 56
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AuthorizationManagers.java

@@ -1,56 +0,0 @@
-/*
- * Copyright 2002-2023 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
- *
- *      https://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.core;
-
-import org.springframework.security.authorization.AuthorityAuthorizationManager;
-
-/**
- * @author Mario Petrovski
- * @since 6.2
- */
-public final class OAuth2AuthorizationManagers {
-
-	private OAuth2AuthorizationManagers() {
-	}
-
-	public static <T> AuthorityAuthorizationManager<T> hasScope(String scope) {
-		verifyScope(scope);
-		return AuthorityAuthorizationManager.hasAuthority("SCOPE_" + scope);
-	}
-
-	public static <T> AuthorityAuthorizationManager<T> hasAnyScope(String... scopes) {
-		verifyScopes(scopes);
-		String[] mappedScopes = new String[scopes.length];
-		for (int i = 0; i < scopes.length; i++) {
-			mappedScopes[i] = "SCOPE_" + scopes[i];
-		}
-		return AuthorityAuthorizationManager.hasAnyAuthority(mappedScopes);
-	}
-
-	private static void verifyScopes(String... scopes) throws IllegalArgumentException {
-		for (String scope : scopes) {
-			verifyScope(scope);
-		}
-	}
-
-	private static void verifyScope(String scope) {
-		if (scope.startsWith("SCOPE_")) {
-			throw new IllegalArgumentException("Scope '" + scope + "' start with 'SCOPE_' prefix.");
-		}
-	}
-
-}

+ 95 - 0
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/authorization/OAuth2AuthorizationManagers.java

@@ -0,0 +1,95 @@
+/*
+ * Copyright 2002-2023 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
+ *
+ *      https://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.core.authorization;
+
+import org.springframework.security.authorization.AuthorityAuthorizationManager;
+import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.util.Assert;
+
+/**
+ * A convenience class for creating OAuth 2.0-specific {@link AuthorizationManager}s.
+ *
+ * @author Mario Petrovski
+ * @author Josh Cummings
+ * @since 6.2
+ * @see AuthorityAuthorizationManager
+ */
+public final class OAuth2AuthorizationManagers {
+
+	private OAuth2AuthorizationManagers() {
+	}
+
+	/**
+	 * Create an {@link AuthorizationManager} that requires an {@link Authentication} to
+	 * have a {@code SCOPE_scope} authority.
+	 *
+	 * <p>
+	 * For example, if you call {@code hasScope("read")}, then this will require that each
+	 * authentication have a {@link org.springframework.security.core.GrantedAuthority}
+	 * whose value is {@code SCOPE_read}.
+	 *
+	 * <p>
+	 * This would equivalent to calling
+	 * {@code AuthorityAuthorizationManager#hasAuthority("SCOPE_read")}.
+	 * @param scope the scope value to require
+	 * @param <T> the secure object
+	 * @return an {@link AuthorizationManager} that requires a {@code "SCOPE_scope"}
+	 * authority
+	 */
+	public static <T> AuthorizationManager<T> hasScope(String scope) {
+		assertScope(scope);
+		return AuthorityAuthorizationManager.hasAuthority("SCOPE_" + scope);
+	}
+
+	/**
+	 * Create an {@link AuthorizationManager} that requires an {@link Authentication} to
+	 * have at least one authority among {@code SCOPE_scope1}, {@code SCOPE_scope2}, ...
+	 * {@code SCOPE_scopeN}.
+	 *
+	 * <p>
+	 * For example, if you call {@code hasAnyScope("read", "write")}, then this will
+	 * require that each authentication have at least a
+	 * {@link org.springframework.security.core.GrantedAuthority} whose value is either
+	 * {@code SCOPE_read} or {@code SCOPE_write}.
+	 *
+	 * <p>
+	 * This would equivalent to calling
+	 * {@code AuthorityAuthorizationManager#hasAnyAuthority("SCOPE_read", "SCOPE_write")}.
+	 * @param scopes the scope values to allow
+	 * @param <T> the secure object
+	 * @return an {@link AuthorizationManager} that requires at least one authority among
+	 * {@code "SCOPE_scope1"}, {@code SCOPE_scope2}, ... {@code SCOPE_scopeN}.
+	 *
+	 */
+	public static <T> AuthorizationManager<T> hasAnyScope(String... scopes) {
+		String[] mappedScopes = new String[scopes.length];
+		for (int i = 0; i < scopes.length; i++) {
+			assertScope(scopes[i]);
+			mappedScopes[i] = "SCOPE_" + scopes[i];
+		}
+		return AuthorityAuthorizationManager.hasAnyAuthority(mappedScopes);
+	}
+
+	private static void assertScope(String scope) {
+		Assert.isTrue(!scope.startsWith("SCOPE_"),
+				() -> scope + " should not start with SCOPE_ since SCOPE_"
+						+ " is automatically prepended when using hasScope and hasAnyScope. Consider using "
+						+ " AuthorityAuthorizationManager#hasAuthority or #hasAnyAuthority instead.");
+	}
+
+}

+ 95 - 0
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/authorization/OAuth2ReactiveAuthorizationManagers.java

@@ -0,0 +1,95 @@
+/*
+ * Copyright 2002-2023 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
+ *
+ *      https://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.core.authorization;
+
+import org.springframework.security.authorization.AuthorityReactiveAuthorizationManager;
+import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.authorization.ReactiveAuthorizationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.util.Assert;
+
+/**
+ * A convenience class for creating OAuth 2.0-specific {@link AuthorizationManager}s.
+ *
+ * @author Josh Cummings
+ * @since 6.2
+ * @see AuthorityReactiveAuthorizationManager
+ */
+public final class OAuth2ReactiveAuthorizationManagers {
+
+	private OAuth2ReactiveAuthorizationManagers() {
+	}
+
+	/**
+	 * Create a {@link ReactiveAuthorizationManager} that requires an
+	 * {@link Authentication} to have a {@code SCOPE_scope} authority.
+	 *
+	 * <p>
+	 * For example, if you call {@code hasScope("read")}, then this will require that each
+	 * authentication have a {@link org.springframework.security.core.GrantedAuthority}
+	 * whose value is {@code SCOPE_read}.
+	 *
+	 * <p>
+	 * This would equivalent to calling
+	 * {@code AuthorityReactiveAuthorizationManager#hasAuthority("SCOPE_read")}.
+	 * @param scope the scope value to require
+	 * @param <T> the secure object
+	 * @return an {@link ReactiveAuthorizationManager} that requires a
+	 * {@code "SCOPE_scope"} authority
+	 */
+	public static <T> ReactiveAuthorizationManager<T> hasScope(String scope) {
+		assertScope(scope);
+		return AuthorityReactiveAuthorizationManager.hasAuthority("SCOPE_" + scope);
+	}
+
+	/**
+	 * Create a {@link ReactiveAuthorizationManager} that requires an
+	 * {@link Authentication} to have at least one authority among {@code SCOPE_scope1},
+	 * {@code SCOPE_scope2}, ... {@code SCOPE_scopeN}.
+	 *
+	 * <p>
+	 * For example, if you call {@code hasAnyScope("read", "write")}, then this will
+	 * require that each authentication have at least a
+	 * {@link org.springframework.security.core.GrantedAuthority} whose value is either
+	 * {@code SCOPE_read} or {@code SCOPE_write}.
+	 *
+	 * <p>
+	 * This would equivalent to calling
+	 * {@code AuthorityReactiveAuthorizationManager#hasAnyAuthority("SCOPE_read", "SCOPE_write")}.
+	 * @param scopes the scope values to allow
+	 * @param <T> the secure object
+	 * @return an {@link ReactiveAuthorizationManager} that requires at least one
+	 * authority among {@code "SCOPE_scope1"}, {@code SCOPE_scope2}, ...
+	 * {@code SCOPE_scopeN}.
+	 */
+	public static <T> ReactiveAuthorizationManager<T> hasAnyScope(String... scopes) {
+		String[] mappedScopes = new String[scopes.length];
+		for (int i = 0; i < scopes.length; i++) {
+			assertScope(scopes[i]);
+			mappedScopes[i] = "SCOPE_" + scopes[i];
+		}
+		return AuthorityReactiveAuthorizationManager.hasAnyAuthority(mappedScopes);
+	}
+
+	private static void assertScope(String scope) {
+		Assert.isTrue(!scope.startsWith("SCOPE_"),
+				() -> scope + " should not start with SCOPE_ since SCOPE_"
+						+ " is automatically prepended when using hasScope and hasAnyScope. Consider using "
+						+ " AuthorityReactiveAuthorizationManager#hasAuthority or #hasAnyAuthority instead.");
+	}
+
+}

+ 0 - 63
oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/OAuth2AuthorizationManagersTests.java

@@ -1,63 +0,0 @@
-/*
- * Copyright 2002-2023 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
- *
- *      https://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.core;
-
-import org.junit.jupiter.api.Test;
-
-import org.springframework.security.authorization.AuthorityAuthorizationManager;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-
-/**
- * Tests for {@link OAuth2AuthorizationManagers}
- *
- * @author Mario Petrovski
- */
-public class OAuth2AuthorizationManagersTests {
-
-	@Test
-	void hasScope_withInvalidScope_shouldThrowIllegalArgumentException() {
-		String scope = "SCOPE_invalid";
-		assertThatExceptionOfType(IllegalArgumentException.class)
-				.isThrownBy(() -> OAuth2AuthorizationManagers.hasScope(scope))
-				.withMessage("Scope 'SCOPE_invalid' start with 'SCOPE_' prefix.");
-	}
-
-	@Test
-	void hasScopes_withInvalidScope_shouldThrowIllegalArgumentException() {
-		String[] scopes = { "read", "write", "SCOPE_invalid" };
-		assertThatExceptionOfType(IllegalArgumentException.class)
-				.isThrownBy(() -> OAuth2AuthorizationManagers.hasAnyScope(scopes))
-				.withMessage("Scope 'SCOPE_invalid' start with 'SCOPE_' prefix.");
-	}
-
-	@Test
-	void hasScope_withValidScope_shouldPass() {
-		String scope = "read";
-		AuthorityAuthorizationManager<Object> authorizationManager = OAuth2AuthorizationManagers.hasScope(scope);
-		assertThat(authorizationManager).isNotNull();
-	}
-
-	@Test
-	void hasScope_withValidScopes_shouldPass() {
-		String[] scopes = { "read", "write" };
-		AuthorityAuthorizationManager<Object> authorizationManager = OAuth2AuthorizationManagers.hasAnyScope(scopes);
-		assertThat(authorizationManager).isNotNull();
-	}
-
-}

+ 76 - 0
oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/authorization/OAuth2AuthorizationManagersTests.java

@@ -0,0 +1,76 @@
+/*
+ * Copyright 2002-2023 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
+ *
+ *      https://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.core.authorization;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.core.Authentication;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+
+/**
+ * Tests for {@link OAuth2AuthorizationManagers}
+ *
+ * @author Mario Petrovski
+ * @author Josh Cummings
+ */
+public class OAuth2AuthorizationManagersTests {
+
+	@Test
+	void hasScopeWhenInvalidScopeThenThrowIllegalArgument() {
+		String scope = "SCOPE_invalid";
+		assertThatExceptionOfType(IllegalArgumentException.class)
+				.isThrownBy(() -> OAuth2AuthorizationManagers.hasScope(scope))
+				.withMessageContaining("SCOPE_invalid should not start with SCOPE_");
+	}
+
+	@Test
+	void hasAnyScopeWhenInvalidScopeThenThrowIllegalArgument() {
+		String[] scopes = { "read", "write", "SCOPE_invalid" };
+		assertThatExceptionOfType(IllegalArgumentException.class)
+				.isThrownBy(() -> OAuth2AuthorizationManagers.hasAnyScope(scopes))
+				.withMessageContaining("SCOPE_invalid should not start with SCOPE_");
+	}
+
+	@Test
+	void hasScopeWhenValidScopeThenAuthorizationManager() {
+		String scope = "read";
+		AuthorizationManager<Object> authorizationManager = OAuth2AuthorizationManagers.hasScope(scope);
+		authorizationManager.verify(() -> hasScope(scope), new Object());
+		assertThatExceptionOfType(AccessDeniedException.class)
+				.isThrownBy(() -> authorizationManager.verify(() -> hasScope("wrong"), new Object()));
+	}
+
+	@Test
+	void hasAnyScopeWhenValidScopesThenAuthorizationManager() {
+		String[] scopes = { "read", "write" };
+		AuthorizationManager<Object> authorizationManager = OAuth2AuthorizationManagers.hasAnyScope(scopes);
+		for (String scope : scopes) {
+			authorizationManager.verify(() -> hasScope(scope), new Object());
+		}
+		assertThatExceptionOfType(AccessDeniedException.class)
+				.isThrownBy(() -> authorizationManager.verify(() -> hasScope("wrong"), new Object()));
+	}
+
+	Authentication hasScope(String scope) {
+		return new TestingAuthenticationToken("user", "pass", "SCOPE_" + scope);
+	}
+
+}

+ 77 - 0
oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/authorization/OAuth2ReactiveAuthorizationManagersTests.java

@@ -0,0 +1,77 @@
+/*
+ * Copyright 2002-2023 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
+ *
+ *      https://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.core.authorization;
+
+import org.junit.jupiter.api.Test;
+import reactor.core.publisher.Mono;
+
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.authorization.ReactiveAuthorizationManager;
+import org.springframework.security.core.Authentication;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+
+/**
+ * Tests for {@link OAuth2ReactiveAuthorizationManagers}
+ *
+ * @author Josh Cummings
+ */
+public class OAuth2ReactiveAuthorizationManagersTests {
+
+	@Test
+	void hasScopeWhenInvalidScopeThenThrowIllegalArgument() {
+		String scope = "SCOPE_invalid";
+		assertThatExceptionOfType(IllegalArgumentException.class)
+				.isThrownBy(() -> OAuth2ReactiveAuthorizationManagers.hasScope(scope))
+				.withMessageContaining("SCOPE_invalid should not start with SCOPE_");
+	}
+
+	@Test
+	void hasAnyScopeWhenInvalidScopeThenThrowIllegalArgument() {
+		String[] scopes = { "read", "write", "SCOPE_invalid" };
+		assertThatExceptionOfType(IllegalArgumentException.class)
+				.isThrownBy(() -> OAuth2ReactiveAuthorizationManagers.hasAnyScope(scopes))
+				.withMessageContaining("SCOPE_invalid should not start with SCOPE_");
+	}
+
+	@Test
+	void hasScopeWhenValidScopeThenAuthorizationManager() {
+		String scope = "read";
+		ReactiveAuthorizationManager<Object> authorizationManager = OAuth2ReactiveAuthorizationManagers.hasScope(scope);
+		authorizationManager.verify(hasScope(scope), new Object()).block();
+		assertThatExceptionOfType(AccessDeniedException.class)
+				.isThrownBy(() -> authorizationManager.verify(hasScope("wrong"), new Object()).block());
+	}
+
+	@Test
+	void hasAnyScopeWhenValidScopesThenAuthorizationManager() {
+		String[] scopes = { "read", "write" };
+		ReactiveAuthorizationManager<Object> authorizationManager = OAuth2ReactiveAuthorizationManagers
+				.hasAnyScope(scopes);
+		for (String scope : scopes) {
+			authorizationManager.verify(hasScope(scope), new Object()).block();
+		}
+		assertThatExceptionOfType(AccessDeniedException.class)
+				.isThrownBy(() -> authorizationManager.verify(hasScope("wrong"), new Object()).block());
+	}
+
+	Mono<Authentication> hasScope(String scope) {
+		return Mono.just(new TestingAuthenticationToken("user", "pass", "SCOPE_" + scope));
+	}
+
+}