Skip to content

Commit 675835e

Browse files
committed
Add AuthorizationManagerFactory.hasAll(Authorities|Roles)
Closes gh-17932
1 parent ebc391c commit 675835e

File tree

8 files changed

+88
-4
lines changed

8 files changed

+88
-4
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,17 @@ public AuthorizationManagerRequestMatcherRegistry hasAnyRole(String... roles) {
296296
return access(this.authorizationManagerFactory.hasAnyRole(roles));
297297
}
298298

299+
/**
300+
* Specifies that a user requires all the provided roles.
301+
* @param roles the roles that the user should have (i.e. ADMIN, USER, etc). Each
302+
* role should not start with ROLE_ since it is automatically prepended already
303+
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
304+
* customizations
305+
*/
306+
public AuthorizationManagerRequestMatcherRegistry hasAllRoles(String... roles) {
307+
return access(this.authorizationManagerFactory.hasAllRoles(roles));
308+
}
309+
299310
/**
300311
* Specifies a user requires an authority.
301312
* @param authority the authority that should be required
@@ -317,6 +328,17 @@ public AuthorizationManagerRequestMatcherRegistry hasAnyAuthority(String... auth
317328
return access(this.authorizationManagerFactory.hasAnyAuthority(authorities));
318329
}
319330

331+
/**
332+
* Specifies that a user requires all the provided authorities.
333+
* @param authorities the authorities that the user should have (i.e. ROLE_USER,
334+
* ROLE_ADMIN, etc)
335+
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
336+
* customizations
337+
*/
338+
public AuthorizationManagerRequestMatcherRegistry hasAllAuthorities(String... authorities) {
339+
return access(this.authorizationManagerFactory.hasAllAuthorities(authorities));
340+
}
341+
320342
/**
321343
* Specify that URLs are allowed by any authenticated user.
322344
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further

config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,11 @@ public void configureWhenCustomAuthorizationManagerFactoryRegisteredThenUsed() {
190190
verify(authorizationManagerFactory).permitAll();
191191
verify(authorizationManagerFactory).denyAll();
192192
verify(authorizationManagerFactory).hasRole("ADMIN");
193+
verify(authorizationManagerFactory).hasAllRoles("hasAllRoles1", "hasAllRoles2");
193194
verify(authorizationManagerFactory).hasAnyRole("USER", "ADMIN");
194195
verify(authorizationManagerFactory).hasAuthority("write");
195196
verify(authorizationManagerFactory).hasAnyAuthority("resource.read", "read");
197+
verify(authorizationManagerFactory).hasAllAuthorities("hasAllAuthorities1", "hasAllAuthorities2");
196198
verify(authorizationManagerFactory).authenticated();
197199
verify(authorizationManagerFactory).fullyAuthenticated();
198200
verify(authorizationManagerFactory).rememberMe();
@@ -823,8 +825,10 @@ private AuthorizationManagerFactory<RequestAuthorizationContext> mockAuthorizati
823825
given(authorizationManagerFactory.denyAll()).willReturn(authorizationManager);
824826
given(authorizationManagerFactory.hasRole(anyString())).willReturn(authorizationManager);
825827
given(authorizationManagerFactory.hasAnyRole(any(String[].class))).willReturn(authorizationManager);
828+
given(authorizationManagerFactory.hasAllRoles(any(String[].class))).willReturn(authorizationManager);
826829
given(authorizationManagerFactory.hasAuthority(anyString())).willReturn(authorizationManager);
827830
given(authorizationManagerFactory.hasAnyAuthority(any(String[].class))).willReturn(authorizationManager);
831+
given(authorizationManagerFactory.hasAllAuthorities(any(String[].class))).willReturn(authorizationManager);
828832
given(authorizationManagerFactory.authenticated()).willReturn(authorizationManager);
829833
given(authorizationManagerFactory.fullyAuthenticated()).willReturn(authorizationManager);
830834
given(authorizationManagerFactory.rememberMe()).willReturn(authorizationManager);
@@ -1116,8 +1120,10 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
11161120
.requestMatchers("/private").denyAll()
11171121
.requestMatchers("/admin").hasRole("ADMIN")
11181122
.requestMatchers("/user").hasAnyRole("USER", "ADMIN")
1123+
.requestMatchers("/hasAllRoles").hasAllRoles("hasAllRoles1", "hasAllRoles2")
11191124
.requestMatchers(HttpMethod.POST, "/resource").hasAuthority("write")
11201125
.requestMatchers("/resource").hasAnyAuthority("resource.read", "read")
1126+
.requestMatchers("/hasAllAuthorities").hasAllAuthorities("hasAllAuthorities1", "hasAllAuthorities2")
11211127
.requestMatchers("/authenticated").authenticated()
11221128
.requestMatchers("/fully-authenticated").fullyAuthenticated()
11231129
.requestMatchers("/remember-me").rememberMe()

core/src/main/java/org/springframework/security/authorization/AuthorizationManagerFactory.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,17 @@ default AuthorizationManager<T> hasAnyRole(String... roles) {
6565
return AuthorityAuthorizationManager.hasAnyRole(roles);
6666
}
6767

68+
/**
69+
* Creates an {@link AuthorizationManager} that requires users to have all the
70+
* provided roles.
71+
* @param roles the roles (automatically prepended with ROLE_) that the user must have
72+
* to allow access (i.e. USER, ADMIN, etc.)
73+
* @return A new {@link AuthorizationManager} instance
74+
*/
75+
default AuthorizationManager<T> hasAllRoles(String... roles) {
76+
return AllAuthoritiesAuthorizationManager.hasAllRoles(roles);
77+
}
78+
6879
/**
6980
* Creates an {@link AuthorizationManager} that requires users to have the specified
7081
* authority.
@@ -87,6 +98,17 @@ default AuthorizationManager<T> hasAnyAuthority(String... authorities) {
8798
return AuthorityAuthorizationManager.hasAnyAuthority(authorities);
8899
}
89100

101+
/**
102+
* Creates an {@link AuthorizationManager} that requires users to have all the
103+
* provided authorities.
104+
* @param authorities the authorities that the user must have to allow access (i.e.
105+
* USER, ADMIN, etc.)
106+
* @return A new {@link AuthorizationManager} instance
107+
*/
108+
default AuthorizationManager<T> hasAllAuthorities(String... authorities) {
109+
return AllAuthoritiesAuthorizationManager.hasAllAuthorities(authorities);
110+
}
111+
90112
/**
91113
* Creates an {@link AuthorizationManager} that allows any authenticated user.
92114
* @return A new {@link AuthorizationManager} instance

core/src/main/java/org/springframework/security/authorization/DefaultAuthorizationManagerFactory.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ public AuthorizationManager<T> hasAnyRole(String... roles) {
7979
return withRoleHierarchy(AuthorityAuthorizationManager.hasAnyRole(this.rolePrefix, roles));
8080
}
8181

82+
@Override
83+
public AuthorizationManager<T> hasAllRoles(String... roles) {
84+
return withRoleHierarchy(AllAuthoritiesAuthorizationManager.hasAllPrefixedAuthorities(this.rolePrefix, roles));
85+
}
86+
8287
@Override
8388
public AuthorizationManager<T> hasAuthority(String authority) {
8489
return withRoleHierarchy(AuthorityAuthorizationManager.hasAuthority(authority));
@@ -89,6 +94,11 @@ public AuthorizationManager<T> hasAnyAuthority(String... authorities) {
8994
return withRoleHierarchy(AuthorityAuthorizationManager.hasAnyAuthority(authorities));
9095
}
9196

97+
@Override
98+
public AuthorizationManager<T> hasAllAuthorities(String... authorities) {
99+
return withRoleHierarchy(AllAuthoritiesAuthorizationManager.hasAllAuthorities(authorities));
100+
}
101+
92102
@Override
93103
public AuthorizationManager<T> authenticated() {
94104
return withTrustResolver(AuthenticatedAuthorizationManager.authenticated());
@@ -114,6 +124,12 @@ private AuthorityAuthorizationManager<T> withRoleHierarchy(AuthorityAuthorizatio
114124
return authorizationManager;
115125
}
116126

127+
private AllAuthoritiesAuthorizationManager<T> withRoleHierarchy(
128+
AllAuthoritiesAuthorizationManager<T> authorizationManager) {
129+
authorizationManager.setRoleHierarchy(this.roleHierarchy);
130+
return authorizationManager;
131+
}
132+
117133
private AuthenticatedAuthorizationManager<T> withTrustResolver(
118134
AuthenticatedAuthorizationManager<T> authorizationManager) {
119135
authorizationManager.setTrustResolver(this.trustResolver);

core/src/test/java/org/springframework/security/authorization/AuthorizationManagerFactoryTests.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ public void hasAnyRoleReturnsAuthorityAuthorizationManagerByDefault() {
5555
assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
5656
}
5757

58+
@Test
59+
public void hasAllRolesReturnsAllAuthoritiesAuthorizationManagerByDefault() {
60+
AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
61+
AuthorizationManager<String> authorizationManager = factory.hasAllRoles("authority1", "authority2");
62+
assertThat(authorizationManager).isInstanceOf(AllAuthoritiesAuthorizationManager.class);
63+
}
64+
5865
@Test
5966
public void hasAuthorityReturnsAuthorityAuthorizationManagerByDefault() {
6067
AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
@@ -69,6 +76,13 @@ public void hasAnyAuthorityReturnsAuthorityAuthorizationManagerByDefault() {
6976
assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
7077
}
7178

79+
@Test
80+
public void hasAllAuthoritiesReturnsAllAuthoritiesAuthorizationManagerByDefault() {
81+
AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();
82+
AuthorizationManager<String> authorizationManager = factory.hasAllAuthorities("authority1", "authority2");
83+
assertThat(authorizationManager).isInstanceOf(AllAuthoritiesAuthorizationManager.class);
84+
}
85+
7286
@Test
7387
public void authenticatedReturnsAuthenticatedAuthorizationManagerByDefault() {
7488
AuthorizationManagerFactory<String> factory = new DefaultAuthorizationManagerFactory<>();

docs/modules/ROOT/pages/servlet/authorization/architecture.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,10 @@ public interface AuthorizationManagerFactory<T> {
157157
AuthorizationManager<T> denyAll();
158158
AuthorizationManager<T> hasRole(String role);
159159
AuthorizationManager<T> hasAnyRole(String... roles);
160+
AuthorizationManager<T> hasAllRoles(String... roles);
160161
AuthorizationManager<T> hasAuthority(String authority);
161162
AuthorizationManager<T> hasAnyAuthority(String... authorities);
163+
AuthorizationManager<T> hasAllAuthorities(String... authorities);
162164
AuthorizationManager<T> authenticated();
163165
AuthorizationManager<T> fullyAuthenticated();
164166
AuthorizationManager<T> rememberMe();

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -720,7 +720,9 @@ As a quick summary, here are the authorization rules built into the DSL:
720720
* `hasAuthority` - The request requires that the `Authentication` have xref:servlet/authorization/architecture.adoc#authz-authorities[a `GrantedAuthority`] that matches the given value
721721
* `hasRole` - A shortcut for `hasAuthority` that prefixes `ROLE_` or whatever is configured as the default prefix
722722
* `hasAnyAuthority` - The request requires that the `Authentication` have a `GrantedAuthority` that matches any of the given values
723-
* `hasAnyRole` - A shortcut for `hasAnyAuthority` that prefixes `ROLE_` or whatever is configured as the default prefix
723+
* `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
724+
* `hasAllRoles` - A shortcut for `hasAllAuthorities` that prefixes `ROLE_` or whatever is configured as the default prefix
725+
* `hasAllAuthorities` - The request requires that the `Authentication` have a `GrantedAuthority` that matches all of the given values
724726
* `access` - The request uses this custom `AuthorizationManager` to determine access
725727

726728
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 {
746748
.dispatcherTypeMatchers(FORWARD, ERROR).permitAll() // <2>
747749
.requestMatchers("/static/**", "/signup", "/about").permitAll() // <3>
748750
.requestMatchers("/admin/**").hasRole("ADMIN") // <4>
749-
.requestMatchers("/db/**").access(allOf(hasAuthority("db"), hasRole("ADMIN"))) // <5>
751+
.requestMatchers("/db/**").hasAllAuthorities("db", "ROLE_ADMIN") // <5>
750752
.anyRequest().denyAll() // <6>
751753
);
752754
@@ -762,7 +764,7 @@ Specifically, any user can access a request if the URL starts with "/static/", e
762764
<4> Any URL that starts with "/admin/" will be restricted to users who have the role "ROLE_ADMIN".
763765
You will notice that since we are invoking the `hasRole` method we do not need to specify the "ROLE_" prefix.
764766
<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".
765-
You will notice that since we are using the `hasRole` expression we do not need to specify the "ROLE_" prefix.
767+
You will notice that since we are using the `hasAllAuthorities` expression we must specify the "ROLE_" prefix.
766768
<6> Any URL that has not already been matched on is denied access.
767769
This is a good strategy if you do not want to accidentally forget to update your authorization rules.
768770

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Each section that follows will indicate the more notable removals as well as the
1616
== Core
1717

1818
* Removed `AuthorizationManager#check` in favor of `AuthorizationManager#authorize`
19-
* Added javadoc:org.springframework.security.authorization.AllAuthoritiesAuthorizationManager[] and javadoc:org.springframework.security.authorization.AllAuthoritiesReactiveAuthorizationManager[]
19+
* 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`]
2020
* 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
2121
* Added `Authentication.Builder` for mutating and merging `Authentication` instances
2222
* Moved Access API (`AccessDecisionManager`, `AccessDecisionVoter`, etc.) to a new module, `spring-security-access`

0 commit comments

Comments
 (0)