diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java index 021398f500..1906da92d4 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java +++ b/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); diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java index 902b6e9909..1dd92a6b5b 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java +++ b/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() { + } + } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java index b61d495ec2..1b9124e2e8 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java +++ b/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() { diff --git a/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java b/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java index da4527a055..e0c35f7c2d 100644 --- a/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java +++ b/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java @@ -124,6 +124,11 @@ public abstract class SecurityExpressionRoot impleme return isGranted(this.authorizationManagerFactory.hasAnyAuthority(authorities)); } + public final boolean hasAllAuthorities(String... authorities) { + AuthorizationManager manager = this.authorizationManagerFactory.hasAllAuthorities(authorities); + return isGranted(manager); + } + @Override public final boolean hasRole(String role) { if (this.authorizationManagerFactory instanceof DefaultAuthorizationManagerFactory) { @@ -155,6 +160,11 @@ public abstract class SecurityExpressionRoot impleme return isGranted(this.authorizationManagerFactory.hasAnyRole(roles)); } + public final boolean hasAllRoles(String... roles) { + AuthorizationManager manager = this.authorizationManagerFactory.hasAllRoles(roles); + return isGranted(manager); + } + @Override public final Authentication getAuthentication() { return this.authentication.get(); diff --git a/core/src/test/java/org/springframework/security/access/expression/SecurityExpressionRootTests.java b/core/src/test/java/org/springframework/security/access/expression/SecurityExpressionRootTests.java index 60668bc65e..2710ba7b5b 100644 --- a/core/src/test/java/org/springframework/security/access/expression/SecurityExpressionRootTests.java +++ b/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) { diff --git a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc index 181c9bfc18..397e3d4b75 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc +++ b/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: diff --git a/docs/modules/ROOT/pages/whats-new.adoc b/docs/modules/ROOT/pages/whats-new.adoc index 04ac8bf525..60b9184f47 100644 --- a/docs/modules/ROOT/pages/whats-new.adoc +++ b/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`