diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java index 958622a09c..c9e6216f4f 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java @@ -296,6 +296,17 @@ public final class AuthorizeHttpRequestsConfigurer { 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 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 { 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 hasAllAuthorities(String... authorities) { + return AllAuthoritiesAuthorizationManager.hasAllAuthorities(authorities); + } + /** * Creates an {@link AuthorizationManager} that allows any authenticated user. * @return A new {@link AuthorizationManager} instance diff --git a/core/src/main/java/org/springframework/security/authorization/DefaultAuthorizationManagerFactory.java b/core/src/main/java/org/springframework/security/authorization/DefaultAuthorizationManagerFactory.java index a7d7d615d1..09ad052a95 100644 --- a/core/src/main/java/org/springframework/security/authorization/DefaultAuthorizationManagerFactory.java +++ b/core/src/main/java/org/springframework/security/authorization/DefaultAuthorizationManagerFactory.java @@ -79,6 +79,11 @@ public final class DefaultAuthorizationManagerFactory hasAllRoles(String... roles) { + return withRoleHierarchy(AllAuthoritiesAuthorizationManager.hasAllPrefixedAuthorities(this.rolePrefix, roles)); + } + @Override public AuthorizationManager hasAuthority(String authority) { return withRoleHierarchy(AuthorityAuthorizationManager.hasAuthority(authority)); @@ -89,6 +94,11 @@ public final class DefaultAuthorizationManagerFactory hasAllAuthorities(String... authorities) { + return withRoleHierarchy(AllAuthoritiesAuthorizationManager.hasAllAuthorities(authorities)); + } + @Override public AuthorizationManager authenticated() { return withTrustResolver(AuthenticatedAuthorizationManager.authenticated()); @@ -114,6 +124,12 @@ public final class DefaultAuthorizationManagerFactory withRoleHierarchy( + AllAuthoritiesAuthorizationManager authorizationManager) { + authorizationManager.setRoleHierarchy(this.roleHierarchy); + return authorizationManager; + } + private AuthenticatedAuthorizationManager withTrustResolver( AuthenticatedAuthorizationManager authorizationManager) { authorizationManager.setTrustResolver(this.trustResolver); diff --git a/core/src/test/java/org/springframework/security/authorization/AuthorizationManagerFactoryTests.java b/core/src/test/java/org/springframework/security/authorization/AuthorizationManagerFactoryTests.java index 8ea9ed24f5..c24c3f5982 100644 --- a/core/src/test/java/org/springframework/security/authorization/AuthorizationManagerFactoryTests.java +++ b/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 factory = new DefaultAuthorizationManagerFactory<>(); + AuthorizationManager authorizationManager = factory.hasAllRoles("authority1", "authority2"); + assertThat(authorizationManager).isInstanceOf(AllAuthoritiesAuthorizationManager.class); + } + @Test public void hasAuthorityReturnsAuthorityAuthorizationManagerByDefault() { AuthorizationManagerFactory factory = new DefaultAuthorizationManagerFactory<>(); @@ -69,6 +76,13 @@ public class AuthorizationManagerFactoryTests { assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class); } + @Test + public void hasAllAuthoritiesReturnsAllAuthoritiesAuthorizationManagerByDefault() { + AuthorizationManagerFactory factory = new DefaultAuthorizationManagerFactory<>(); + AuthorizationManager authorizationManager = factory.hasAllAuthorities("authority1", "authority2"); + assertThat(authorizationManager).isInstanceOf(AllAuthoritiesAuthorizationManager.class); + } + @Test public void authenticatedReturnsAuthenticatedAuthorizationManagerByDefault() { AuthorizationManagerFactory factory = new DefaultAuthorizationManagerFactory<>(); diff --git a/docs/modules/ROOT/pages/servlet/authorization/architecture.adoc b/docs/modules/ROOT/pages/servlet/authorization/architecture.adoc index ee6bf7dd63..2955a7c028 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/architecture.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/architecture.adoc @@ -157,8 +157,10 @@ public interface AuthorizationManagerFactory { AuthorizationManager denyAll(); AuthorizationManager hasRole(String role); AuthorizationManager hasAnyRole(String... roles); + AuthorizationManager hasAllRoles(String... roles); AuthorizationManager hasAuthority(String authority); AuthorizationManager hasAnyAuthority(String... authorities); + AuthorizationManager hasAllAuthorities(String... authorities); AuthorizationManager authenticated(); AuthorizationManager fullyAuthenticated(); AuthorizationManager rememberMe(); diff --git a/docs/modules/ROOT/pages/servlet/authorization/authorize-http-requests.adoc b/docs/modules/ROOT/pages/servlet/authorization/authorize-http-requests.adoc index c9d687b79a..a424da0629 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/authorize-http-requests.adoc +++ b/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. diff --git a/docs/modules/ROOT/pages/whats-new.adoc b/docs/modules/ROOT/pages/whats-new.adoc index 0f103b0e9f..04ac8bf525 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[] +* 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`