Kaynağa Gözat

Add hasAll(Roles|Authorities) to SecurityExpressionRoot

This adds support for hasAllRoles and hasAllAuthorities to method security
expressions.

Issue gh-17932
Rob Winch 1 ay önce
ebeveyn
işleme
9eaadcc70d

+ 6 - 0
config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java

@@ -93,6 +93,12 @@ public interface MethodSecurityService {
 	@PreAuthorize("hasRole('USER')")
 	void preAuthorizeUser();
 
+	@PreAuthorize("hasAllRoles('USER', 'ADMIN')")
+	void hasAllRolesUserAdmin();
+
+	@PreAuthorize("hasAllAuthorities('ROLE_USER', 'ROLE_ADMIN')")
+	void hasAllAuthoritiesRoleUserRoleAdmin();
+
 	@PreAuthorize("hasPermission(#object,'read')")
 	String hasPermission(String object);
 

+ 8 - 0
config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java

@@ -203,4 +203,12 @@ public class MethodSecurityServiceImpl implements MethodSecurityService {
 		return "ok";
 	}
 
+	@Override
+	public void hasAllRolesUserAdmin() {
+	}
+
+	@Override
+	public void hasAllAuthoritiesRoleUserRoleAdmin() {
+	}
+
 }

+ 46 - 0
config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java

@@ -282,6 +282,52 @@ public class PrePostMethodSecurityConfigurationTests {
 		verify(strategy, atLeastOnce()).getContext();
 	}
 
+	@WithMockUser(roles = { "ADMIN", "USER" })
+	@Test
+	public void hasAllAuthoritiesRoleUserRoleAdminWhenGranted() {
+		this.spring.register(MethodSecurityServiceConfig.class).autowire();
+		this.methodSecurityService.hasAllAuthoritiesRoleUserRoleAdmin();
+	}
+
+	@WithMockUser(roles = { "USER" })
+	@Test
+	public void hasAllAuthoritiesRoleUserRoleAdminWhenMissingOneThenDenied() {
+		this.spring.register(MethodSecurityServiceConfig.class).autowire();
+		assertThatExceptionOfType(AccessDeniedException.class)
+			.isThrownBy(this.methodSecurityService::hasAllAuthoritiesRoleUserRoleAdmin);
+	}
+
+	@WithMockUser(roles = { "OTHER" })
+	@Test
+	public void hasAllAuthoritiesRoleUserRoleAdminWhenAllThenDenied() {
+		this.spring.register(MethodSecurityServiceConfig.class).autowire();
+		assertThatExceptionOfType(AccessDeniedException.class)
+			.isThrownBy(this.methodSecurityService::hasAllAuthoritiesRoleUserRoleAdmin);
+	}
+
+	@WithMockUser(roles = { "ADMIN", "USER" })
+	@Test
+	public void hasAllRolesRoleUserRoleAdminWhenGranted() {
+		this.spring.register(MethodSecurityServiceConfig.class).autowire();
+		this.methodSecurityService.hasAllRolesUserAdmin();
+	}
+
+	@WithMockUser(roles = { "USER" })
+	@Test
+	public void hasAllRolesRoleUserRoleAdminWhenMissingOneThenDenied() {
+		this.spring.register(MethodSecurityServiceConfig.class).autowire();
+		assertThatExceptionOfType(AccessDeniedException.class)
+			.isThrownBy(this.methodSecurityService::hasAllRolesUserAdmin);
+	}
+
+	@WithMockUser(roles = { "OTHER" })
+	@Test
+	public void hasAllRolesRoleUserRoleAdminWhenAllThenDenied() {
+		this.spring.register(MethodSecurityServiceConfig.class).autowire();
+		assertThatExceptionOfType(AccessDeniedException.class)
+			.isThrownBy(this.methodSecurityService::hasAllRolesUserAdmin);
+	}
+
 	@WithMockUser(authorities = "PREFIX_ADMIN")
 	@Test
 	public void preAuthorizeAdminWhenRoleAdminAndCustomPrefixThenPasses() {

+ 10 - 0
core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java

@@ -124,6 +124,11 @@ public abstract class SecurityExpressionRoot<T extends @Nullable Object> impleme
 		return isGranted(this.authorizationManagerFactory.hasAnyAuthority(authorities));
 	}
 
+	public final boolean hasAllAuthorities(String... authorities) {
+		AuthorizationManager<T> manager = this.authorizationManagerFactory.hasAllAuthorities(authorities);
+		return isGranted(manager);
+	}
+
 	@Override
 	public final boolean hasRole(String role) {
 		if (this.authorizationManagerFactory instanceof DefaultAuthorizationManagerFactory<T>) {
@@ -155,6 +160,11 @@ public abstract class SecurityExpressionRoot<T extends @Nullable Object> impleme
 		return isGranted(this.authorizationManagerFactory.hasAnyRole(roles));
 	}
 
+	public final boolean hasAllRoles(String... roles) {
+		AuthorizationManager<T> manager = this.authorizationManagerFactory.hasAllRoles(roles);
+		return isGranted(manager);
+	}
+
 	@Override
 	public final Authentication getAuthentication() {
 		return this.authentication.get();

+ 16 - 0
core/src/test/java/org/springframework/security/access/expression/SecurityExpressionRootTests.java

@@ -128,6 +128,14 @@ public class SecurityExpressionRootTests {
 		assertThat(this.root.hasAnyRole("ROLE_A")).isTrue();
 	}
 
+	@Test
+	public void hasAllRoles() {
+		assertThat(this.root.hasAllRoles("A")).isTrue();
+		assertThat(this.root.hasAllRoles("A", "B")).isTrue();
+		assertThat(this.root.hasAllRoles("NO")).isFalse();
+		assertThat(this.root.hasAllRoles("A", "NO")).isFalse();
+	}
+
 	@Test
 	public void hasAuthorityDoesNotAddDefaultPrefix() {
 		assertThat(this.root.hasAuthority("A")).isFalse();
@@ -135,6 +143,14 @@ public class SecurityExpressionRootTests {
 		assertThat(this.root.hasAnyAuthority("ROLE_A", "NOT")).isTrue();
 	}
 
+	@Test
+	public void hasAllAuthorities() {
+		assertThat(this.root.hasAllAuthorities("ROLE_A")).isTrue();
+		assertThat(this.root.hasAllAuthorities("ROLE_A", "ROLE_B")).isTrue();
+		assertThat(this.root.hasAllAuthorities("ROLE_NO")).isFalse();
+		assertThat(this.root.hasAllAuthorities("ROLE_A", "ROLE_NO")).isFalse();
+	}
+
 	@Test
 	void isAuthenticatedWhenAuthenticatedNullThenException() {
 		this.root = new SecurityExpressionRoot((Authentication) null) {

+ 2 - 1
docs/modules/ROOT/pages/servlet/authorization/method-security.adoc

@@ -1,4 +1,3 @@
-
 [[jc-method]]
 = Method Security
 :figures: servlet/authorization
@@ -1828,6 +1827,8 @@ What follows is a quick overview of the most common methods:
 * `hasRole` - A shortcut for `hasAuthority` that prefixes `ROLE_` or whatever is configured as the default prefix
 * `hasAnyAuthority` - The method 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
+* `hasAllAuthorities` - The method requires that the `Authentication` have ``GrantedAuthority``s that matches all of the given values
+* `hasAllRoles` - A shortcut for `hasAllAuthorities` that prefixes `ROLE_` or whatever is configured as the default prefix
 * `hasPermission` - A hook into your `PermissionEvaluator` instance for doing object-level authorization
 
 And here is a brief look at the most common fields:

+ 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[] along with corresponding methods for xref:servlet/authorization/authorize-http-requests.adoc#authorize-requests[Authorizing `HttpServletRequests`]
+* 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`] and xref:servlet/authorization/method-security.adoc#using-authorization-expression-fields-and-methods[method security expressions].
 * 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`