1
0
mirror of synced 2026-05-22 21:33:16 +00:00

Add hasAll(Roles|Authorities) to SecurityExpressionRoot

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

Issue gh-17932
This commit is contained in:
Rob Winch
2025-09-19 09:33:50 -05:00
parent bce8049815
commit 9eaadcc70d
7 changed files with 89 additions and 2 deletions
@@ -93,6 +93,12 @@ public interface MethodSecurityService {
@PreAuthorize("hasRole('USER')") @PreAuthorize("hasRole('USER')")
void preAuthorizeUser(); void preAuthorizeUser();
@PreAuthorize("hasAllRoles('USER', 'ADMIN')")
void hasAllRolesUserAdmin();
@PreAuthorize("hasAllAuthorities('ROLE_USER', 'ROLE_ADMIN')")
void hasAllAuthoritiesRoleUserRoleAdmin();
@PreAuthorize("hasPermission(#object,'read')") @PreAuthorize("hasPermission(#object,'read')")
String hasPermission(String object); String hasPermission(String object);
@@ -203,4 +203,12 @@ public class MethodSecurityServiceImpl implements MethodSecurityService {
return "ok"; return "ok";
} }
@Override
public void hasAllRolesUserAdmin() {
}
@Override
public void hasAllAuthoritiesRoleUserRoleAdmin() {
}
} }
@@ -282,6 +282,52 @@ public class PrePostMethodSecurityConfigurationTests {
verify(strategy, atLeastOnce()).getContext(); 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") @WithMockUser(authorities = "PREFIX_ADMIN")
@Test @Test
public void preAuthorizeAdminWhenRoleAdminAndCustomPrefixThenPasses() { public void preAuthorizeAdminWhenRoleAdminAndCustomPrefixThenPasses() {
@@ -124,6 +124,11 @@ public abstract class SecurityExpressionRoot<T extends @Nullable Object> impleme
return isGranted(this.authorizationManagerFactory.hasAnyAuthority(authorities)); return isGranted(this.authorizationManagerFactory.hasAnyAuthority(authorities));
} }
public final boolean hasAllAuthorities(String... authorities) {
AuthorizationManager<T> manager = this.authorizationManagerFactory.hasAllAuthorities(authorities);
return isGranted(manager);
}
@Override @Override
public final boolean hasRole(String role) { public final boolean hasRole(String role) {
if (this.authorizationManagerFactory instanceof DefaultAuthorizationManagerFactory<T>) { 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)); return isGranted(this.authorizationManagerFactory.hasAnyRole(roles));
} }
public final boolean hasAllRoles(String... roles) {
AuthorizationManager<T> manager = this.authorizationManagerFactory.hasAllRoles(roles);
return isGranted(manager);
}
@Override @Override
public final Authentication getAuthentication() { public final Authentication getAuthentication() {
return this.authentication.get(); return this.authentication.get();
@@ -128,6 +128,14 @@ public class SecurityExpressionRootTests {
assertThat(this.root.hasAnyRole("ROLE_A")).isTrue(); 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 @Test
public void hasAuthorityDoesNotAddDefaultPrefix() { public void hasAuthorityDoesNotAddDefaultPrefix() {
assertThat(this.root.hasAuthority("A")).isFalse(); assertThat(this.root.hasAuthority("A")).isFalse();
@@ -135,6 +143,14 @@ public class SecurityExpressionRootTests {
assertThat(this.root.hasAnyAuthority("ROLE_A", "NOT")).isTrue(); 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 @Test
void isAuthenticatedWhenAuthenticatedNullThenException() { void isAuthenticatedWhenAuthenticatedNullThenException() {
this.root = new SecurityExpressionRoot((Authentication) null) { this.root = new SecurityExpressionRoot((Authentication) null) {
@@ -1,4 +1,3 @@
[[jc-method]] [[jc-method]]
= Method Security = Method Security
:figures: servlet/authorization :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 * `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 * `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 * `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 * `hasPermission` - A hook into your `PermissionEvaluator` instance for doing object-level authorization
And here is a brief look at the most common fields: And here is a brief look at the most common fields:
+1 -1
View File
@@ -16,7 +16,7 @@ Each section that follows will indicate the more notable removals as well as the
== Core == Core
* Removed `AuthorizationManager#check` in favor of `AuthorizationManager#authorize` * 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 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 * Added `Authentication.Builder` for mutating and merging `Authentication` instances
* Moved Access API (`AccessDecisionManager`, `AccessDecisionVoter`, etc.) to a new module, `spring-security-access` * Moved Access API (`AccessDecisionManager`, `AccessDecisionVoter`, etc.) to a new module, `spring-security-access`