Переглянути джерело

Add AuthorizationManagerFactory.hasAll(Authorities|Roles)

Closes gh-17932
Rob Winch 1 місяць тому
батько
коміт
675835e525

+ 22 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java

@@ -296,6 +296,17 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 			return access(this.authorizationManagerFactory.hasAnyRole(roles));
 		}
 
+		/**
+		 * Specifies that a user requires all the provided roles.
+		 * @param roles the roles that the user should have (i.e. ADMIN, USER, etc). Each
+		 * role should not start with ROLE_ since it is automatically prepended already
+		 * @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
+		 * customizations
+		 */
+		public AuthorizationManagerRequestMatcherRegistry hasAllRoles(String... roles) {
+			return access(this.authorizationManagerFactory.hasAllRoles(roles));
+		}
+
 		/**
 		 * Specifies a user requires an authority.
 		 * @param authority the authority that should be required
@@ -317,6 +328,17 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 			return access(this.authorizationManagerFactory.hasAnyAuthority(authorities));
 		}
 
+		/**
+		 * Specifies that a user requires all the provided authorities.
+		 * @param authorities the authorities that the user should have (i.e. ROLE_USER,
+		 * ROLE_ADMIN, etc)
+		 * @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
+		 * customizations
+		 */
+		public AuthorizationManagerRequestMatcherRegistry hasAllAuthorities(String... authorities) {
+			return access(this.authorizationManagerFactory.hasAllAuthorities(authorities));
+		}
+
 		/**
 		 * Specify that URLs are allowed by any authenticated user.
 		 * @return the {@link AuthorizationManagerRequestMatcherRegistry} for further

+ 6 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java

@@ -190,9 +190,11 @@ public class AuthorizeHttpRequestsConfigurerTests {
 		verify(authorizationManagerFactory).permitAll();
 		verify(authorizationManagerFactory).denyAll();
 		verify(authorizationManagerFactory).hasRole("ADMIN");
+		verify(authorizationManagerFactory).hasAllRoles("hasAllRoles1", "hasAllRoles2");
 		verify(authorizationManagerFactory).hasAnyRole("USER", "ADMIN");
 		verify(authorizationManagerFactory).hasAuthority("write");
 		verify(authorizationManagerFactory).hasAnyAuthority("resource.read", "read");
+		verify(authorizationManagerFactory).hasAllAuthorities("hasAllAuthorities1", "hasAllAuthorities2");
 		verify(authorizationManagerFactory).authenticated();
 		verify(authorizationManagerFactory).fullyAuthenticated();
 		verify(authorizationManagerFactory).rememberMe();
@@ -823,8 +825,10 @@ public class AuthorizeHttpRequestsConfigurerTests {
 		given(authorizationManagerFactory.denyAll()).willReturn(authorizationManager);
 		given(authorizationManagerFactory.hasRole(anyString())).willReturn(authorizationManager);
 		given(authorizationManagerFactory.hasAnyRole(any(String[].class))).willReturn(authorizationManager);
+		given(authorizationManagerFactory.hasAllRoles(any(String[].class))).willReturn(authorizationManager);
 		given(authorizationManagerFactory.hasAuthority(anyString())).willReturn(authorizationManager);
 		given(authorizationManagerFactory.hasAnyAuthority(any(String[].class))).willReturn(authorizationManager);
+		given(authorizationManagerFactory.hasAllAuthorities(any(String[].class))).willReturn(authorizationManager);
 		given(authorizationManagerFactory.authenticated()).willReturn(authorizationManager);
 		given(authorizationManagerFactory.fullyAuthenticated()).willReturn(authorizationManager);
 		given(authorizationManagerFactory.rememberMe()).willReturn(authorizationManager);
@@ -1116,8 +1120,10 @@ public class AuthorizeHttpRequestsConfigurerTests {
 					.requestMatchers("/private").denyAll()
 					.requestMatchers("/admin").hasRole("ADMIN")
 					.requestMatchers("/user").hasAnyRole("USER", "ADMIN")
+					.requestMatchers("/hasAllRoles").hasAllRoles("hasAllRoles1", "hasAllRoles2")
 					.requestMatchers(HttpMethod.POST, "/resource").hasAuthority("write")
 					.requestMatchers("/resource").hasAnyAuthority("resource.read", "read")
+					.requestMatchers("/hasAllAuthorities").hasAllAuthorities("hasAllAuthorities1", "hasAllAuthorities2")
 					.requestMatchers("/authenticated").authenticated()
 					.requestMatchers("/fully-authenticated").fullyAuthenticated()
 					.requestMatchers("/remember-me").rememberMe()

+ 22 - 0
core/src/main/java/org/springframework/security/authorization/AuthorizationManagerFactory.java

@@ -65,6 +65,17 @@ public interface AuthorizationManagerFactory<T extends @Nullable Object> {
 		return AuthorityAuthorizationManager.hasAnyRole(roles);
 	}
 
+	/**
+	 * Creates an {@link AuthorizationManager} that requires users to have all the
+	 * provided roles.
+	 * @param roles the roles (automatically prepended with ROLE_) that the user must have
+	 * to allow access (i.e. USER, ADMIN, etc.)
+	 * @return A new {@link AuthorizationManager} instance
+	 */
+	default AuthorizationManager<T> hasAllRoles(String... roles) {
+		return AllAuthoritiesAuthorizationManager.hasAllRoles(roles);
+	}
+
 	/**
 	 * Creates an {@link AuthorizationManager} that requires users to have the specified
 	 * authority.
@@ -87,6 +98,17 @@ public interface AuthorizationManagerFactory<T extends @Nullable Object> {
 		return AuthorityAuthorizationManager.hasAnyAuthority(authorities);
 	}
 
+	/**
+	 * Creates an {@link AuthorizationManager} that requires users to have all the
+	 * provided authorities.
+	 * @param authorities the authorities that the user must have to allow access (i.e.
+	 * USER, ADMIN, etc.)
+	 * @return A new {@link AuthorizationManager} instance
+	 */
+	default AuthorizationManager<T> hasAllAuthorities(String... authorities) {
+		return AllAuthoritiesAuthorizationManager.hasAllAuthorities(authorities);
+	}
+
 	/**
 	 * Creates an {@link AuthorizationManager} that allows any authenticated user.
 	 * @return A new {@link AuthorizationManager} instance

+ 16 - 0
core/src/main/java/org/springframework/security/authorization/DefaultAuthorizationManagerFactory.java

@@ -79,6 +79,11 @@ public final class DefaultAuthorizationManagerFactory<T extends @Nullable Object
 		return withRoleHierarchy(AuthorityAuthorizationManager.hasAnyRole(this.rolePrefix, roles));
 	}
 
+	@Override
+	public AuthorizationManager<T> hasAllRoles(String... roles) {
+		return withRoleHierarchy(AllAuthoritiesAuthorizationManager.hasAllPrefixedAuthorities(this.rolePrefix, roles));
+	}
+
 	@Override
 	public AuthorizationManager<T> hasAuthority(String authority) {
 		return withRoleHierarchy(AuthorityAuthorizationManager.hasAuthority(authority));
@@ -89,6 +94,11 @@ public final class DefaultAuthorizationManagerFactory<T extends @Nullable Object
 		return withRoleHierarchy(AuthorityAuthorizationManager.hasAnyAuthority(authorities));
 	}
 
+	@Override
+	public AuthorizationManager<T> hasAllAuthorities(String... authorities) {
+		return withRoleHierarchy(AllAuthoritiesAuthorizationManager.hasAllAuthorities(authorities));
+	}
+
 	@Override
 	public AuthorizationManager<T> authenticated() {
 		return withTrustResolver(AuthenticatedAuthorizationManager.authenticated());
@@ -114,6 +124,12 @@ public final class DefaultAuthorizationManagerFactory<T extends @Nullable Object
 		return authorizationManager;
 	}
 
+	private AllAuthoritiesAuthorizationManager<T> withRoleHierarchy(
+			AllAuthoritiesAuthorizationManager<T> authorizationManager) {
+		authorizationManager.setRoleHierarchy(this.roleHierarchy);
+		return authorizationManager;
+	}
+
 	private AuthenticatedAuthorizationManager<T> withTrustResolver(
 			AuthenticatedAuthorizationManager<T> authorizationManager) {
 		authorizationManager.setTrustResolver(this.trustResolver);

+ 14 - 0
core/src/test/java/org/springframework/security/authorization/AuthorizationManagerFactoryTests.java

@@ -55,6 +55,13 @@ public class AuthorizationManagerFactoryTests {
 		assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
 	}
 
+	@Test
+	public void hasAllRolesReturnsAllAuthoritiesAuthorizationManagerByDefault() {
+		AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
+		AuthorizationManager<String> authorizationManager = factory.hasAllRoles("authority1", "authority2");
+		assertThat(authorizationManager).isInstanceOf(AllAuthoritiesAuthorizationManager.class);
+	}
+
 	@Test
 	public void hasAuthorityReturnsAuthorityAuthorizationManagerByDefault() {
 		AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
@@ -69,6 +76,13 @@ public class AuthorizationManagerFactoryTests {
 		assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
 	}
 
+	@Test
+	public void hasAllAuthoritiesReturnsAllAuthoritiesAuthorizationManagerByDefault() {
+		AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
+		AuthorizationManager<String> authorizationManager = factory.hasAllAuthorities("authority1", "authority2");
+		assertThat(authorizationManager).isInstanceOf(AllAuthoritiesAuthorizationManager.class);
+	}
+
 	@Test
 	public void authenticatedReturnsAuthenticatedAuthorizationManagerByDefault() {
 		AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();

+ 2 - 0
docs/modules/ROOT/pages/servlet/authorization/architecture.adoc

@@ -157,8 +157,10 @@ public interface AuthorizationManagerFactory<T> {
 	AuthorizationManager<T> denyAll();
 	AuthorizationManager<T> hasRole(String role);
 	AuthorizationManager<T> hasAnyRole(String... roles);
+	AuthorizationManager<T> hasAllRoles(String... roles);
 	AuthorizationManager<T> hasAuthority(String authority);
 	AuthorizationManager<T> hasAnyAuthority(String... authorities);
+	AuthorizationManager<T> hasAllAuthorities(String... authorities);
 	AuthorizationManager<T> authenticated();
 	AuthorizationManager<T> fullyAuthenticated();
 	AuthorizationManager<T> rememberMe();

+ 5 - 3
docs/modules/ROOT/pages/servlet/authorization/authorize-http-requests.adoc

@@ -720,7 +720,9 @@ As a quick summary, here are the authorization rules built into the DSL:
 * `hasAuthority` - The request requires that the `Authentication` have xref:servlet/authorization/architecture.adoc#authz-authorities[a `GrantedAuthority`] that matches the given value
 * `hasRole` - A shortcut for `hasAuthority` that prefixes `ROLE_` or whatever is configured as the default prefix
 * `hasAnyAuthority` - The request requires that the `Authentication` have a `GrantedAuthority` that matches any of the given values
-* `hasAnyRole` - A shortcut for `hasAnyAuthority` that prefixes `ROLE_` or whatever is configured as the default prefix
+* `hasAnyRole` - A shortcut for `hasAnyAuthority` that prefixes `ROLE_` or whatever is configured as the default prefix* `hasAnyAuthority` - The request requires that the `Authentication` have a `GrantedAuthority` that matches any of the given values
+* `hasAllRoles` - A shortcut for `hasAllAuthorities` that prefixes `ROLE_` or whatever is configured as the default prefix
+* `hasAllAuthorities` - The request requires that the `Authentication` have a `GrantedAuthority` that matches all of the given values
 * `access` - The request uses this custom `AuthorizationManager` to determine access
 
 Having now learned the patterns, rules, and how they can be paired together, you should be able to understand what is going on in this more complex example:
@@ -746,7 +748,7 @@ SecurityFilterChain web(HttpSecurity http) throws Exception {
             .dispatcherTypeMatchers(FORWARD, ERROR).permitAll() // <2>
 			.requestMatchers("/static/**", "/signup", "/about").permitAll()         // <3>
 			.requestMatchers("/admin/**").hasRole("ADMIN")                             // <4>
-			.requestMatchers("/db/**").access(allOf(hasAuthority("db"), hasRole("ADMIN")))   // <5>
+			.requestMatchers("/db/**").hasAllAuthorities("db", "ROLE_ADMIN")   // <5>
 			.anyRequest().denyAll()                                                // <6>
 		);
 
@@ -762,7 +764,7 @@ Specifically, any user can access a request if the URL starts with "/static/", e
 <4> Any URL that starts with "/admin/" will be restricted to users who have the role "ROLE_ADMIN".
 You will notice that since we are invoking the `hasRole` method we do not need to specify the "ROLE_" prefix.
 <5> Any URL that starts with "/db/" requires the user to have both been granted the "db" permission as well as be a "ROLE_ADMIN".
-You will notice that since we are using the `hasRole` expression we do not need to specify the "ROLE_" prefix.
+You will notice that since we are using the `hasAllAuthorities` expression we must specify the "ROLE_" prefix.
 <6> Any URL that has not already been matched on is denied access.
 This is a good strategy if you do not want to accidentally forget to update your authorization rules.
 

+ 1 - 1
docs/modules/ROOT/pages/whats-new.adoc

@@ -16,7 +16,7 @@ Each section that follows will indicate the more notable removals as well as the
 == Core
 
 * Removed `AuthorizationManager#check` in favor of `AuthorizationManager#authorize`
-* Added javadoc:org.springframework.security.authorization.AllAuthoritiesAuthorizationManager[] and javadoc:org.springframework.security.authorization.AllAuthoritiesReactiveAuthorizationManager[]
+* Added javadoc:org.springframework.security.authorization.AllAuthoritiesAuthorizationManager[] and javadoc:org.springframework.security.authorization.AllAuthoritiesReactiveAuthorizationManager[] along with corresponding methods for xref:servlet/authorization/authorize-http-requests.adoc#authorize-requests[Authorizing `HttpServletRequests`]
 * Added xref:servlet/authorization/architecture.adoc#authz-authorization-manager-factory[`AuthorizationManagerFactory`] for creating `AuthorizationManager` instances in xref:servlet/authorization/authorize-http-requests.adoc#customizing-authorization-managers[request-based] and xref:servlet/authorization/method-security.adoc#customizing-authorization-managers[method-based] authorization components
 * Added `Authentication.Builder` for mutating and merging `Authentication` instances
 * Moved Access API (`AccessDecisionManager`, `AccessDecisionVoter`, etc.) to a new module, `spring-security-access`