Skip to content

Commit d84b8d2

Browse files
evgeniychebanjzheaux
authored andcommitted
AuthorizeHttpRequestsConfigurer.AuthorizedUrl.hasRole should look up for a RoleHierarchy bean in the context
Closes gh-12473
1 parent ce11015 commit d84b8d2

File tree

2 files changed

+57
-6
lines changed

2 files changed

+57
-6
lines changed

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

+19-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,11 +17,14 @@
1717
package org.springframework.security.config.annotation.web.configurers;
1818

1919
import java.util.List;
20+
import java.util.function.Supplier;
2021

2122
import io.micrometer.observation.ObservationRegistry;
2223
import jakarta.servlet.http.HttpServletRequest;
2324

2425
import org.springframework.context.ApplicationContext;
26+
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
27+
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
2528
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
2629
import org.springframework.security.authorization.AuthorityAuthorizationManager;
2730
import org.springframework.security.authorization.AuthorizationDecision;
@@ -38,6 +41,7 @@
3841
import org.springframework.security.web.util.matcher.RequestMatcher;
3942
import org.springframework.security.web.util.matcher.RequestMatcherEntry;
4043
import org.springframework.util.Assert;
44+
import org.springframework.util.function.SingletonSupplier;
4145

4246
/**
4347
* Adds a URL based authorization using {@link AuthorizationManager}.
@@ -56,6 +60,8 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
5660

5761
private final AuthorizationEventPublisher publisher;
5862

63+
private final Supplier<RoleHierarchy> roleHierarchy;
64+
5965
/**
6066
* Creates an instance.
6167
* @param context the {@link ApplicationContext} to use
@@ -68,6 +74,8 @@ public AuthorizeHttpRequestsConfigurer(ApplicationContext context) {
6874
else {
6975
this.publisher = new SpringAuthorizationEventPublisher(context);
7076
}
77+
this.roleHierarchy = SingletonSupplier.of(() -> (context.getBeanNamesForType(RoleHierarchy.class).length > 0)
78+
? context.getBean(RoleHierarchy.class) : new NullRoleHierarchy());
7179
}
7280

7381
/**
@@ -251,7 +259,7 @@ public AuthorizationManagerRequestMatcherRegistry denyAll() {
251259
* customizations
252260
*/
253261
public AuthorizationManagerRequestMatcherRegistry hasRole(String role) {
254-
return access(AuthorityAuthorizationManager.hasRole(role));
262+
return access(withRoleHierarchy(AuthorityAuthorizationManager.hasRole(role)));
255263
}
256264

257265
/**
@@ -263,7 +271,7 @@ public AuthorizationManagerRequestMatcherRegistry hasRole(String role) {
263271
* customizations
264272
*/
265273
public AuthorizationManagerRequestMatcherRegistry hasAnyRole(String... roles) {
266-
return access(AuthorityAuthorizationManager.hasAnyRole(roles));
274+
return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAnyRole(roles)));
267275
}
268276

269277
/**
@@ -273,7 +281,7 @@ public AuthorizationManagerRequestMatcherRegistry hasAnyRole(String... roles) {
273281
* customizations
274282
*/
275283
public AuthorizationManagerRequestMatcherRegistry hasAuthority(String authority) {
276-
return access(AuthorityAuthorizationManager.hasAuthority(authority));
284+
return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAuthority(authority)));
277285
}
278286

279287
/**
@@ -284,7 +292,13 @@ public AuthorizationManagerRequestMatcherRegistry hasAuthority(String authority)
284292
* customizations
285293
*/
286294
public AuthorizationManagerRequestMatcherRegistry hasAnyAuthority(String... authorities) {
287-
return access(AuthorityAuthorizationManager.hasAnyAuthority(authorities));
295+
return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAnyAuthority(authorities)));
296+
}
297+
298+
private AuthorityAuthorizationManager<RequestAuthorizationContext> withRoleHierarchy(
299+
AuthorityAuthorizationManager<RequestAuthorizationContext> manager) {
300+
manager.setRoleHierarchy(AuthorizeHttpRequestsConfigurer.this.roleHierarchy.get());
301+
return manager;
288302
}
289303

290304
/**

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

+38-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,8 @@
2626
import org.springframework.beans.factory.annotation.Autowired;
2727
import org.springframework.context.annotation.Bean;
2828
import org.springframework.context.annotation.Configuration;
29+
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
30+
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
2931
import org.springframework.security.authentication.RememberMeAuthenticationToken;
3032
import org.springframework.security.authentication.TestAuthentication;
3133
import org.springframework.security.authorization.AuthorizationDecision;
@@ -272,6 +274,17 @@ public void getWhenHasRoleUserConfiguredAndRoleIsAdminThenRespondsWithForbidden(
272274
this.mvc.perform(requestWithAdmin).andExpect(status().isForbidden());
273275
}
274276

277+
@Test
278+
public void getWhenHasRoleUserAndRoleHierarchyConfiguredThenGreaterRoleTakesPrecedence() throws Exception {
279+
this.spring.register(RoleHierarchyUserConfig.class, BasicController.class).autowire();
280+
// @formatter:off
281+
MockHttpServletRequestBuilder requestWithAdmin = get("/")
282+
.with(user("user")
283+
.roles("ADMIN"));
284+
// @formatter:on
285+
this.mvc.perform(requestWithAdmin).andExpect(status().isOk());
286+
}
287+
275288
@Test
276289
public void getWhenRoleUserOrAdminConfiguredAndRoleIsUserThenRespondsWithOk() throws Exception {
277290
this.spring.register(RoleUserOrAdminConfig.class, BasicController.class).autowire();
@@ -770,6 +783,30 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
770783

771784
}
772785

786+
@Configuration
787+
@EnableWebSecurity
788+
static class RoleHierarchyUserConfig {
789+
790+
@Bean
791+
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
792+
// @formatter:off
793+
return http
794+
.authorizeHttpRequests((requests) -> requests
795+
.anyRequest().hasRole("USER")
796+
)
797+
.build();
798+
// @formatter:on
799+
}
800+
801+
@Bean
802+
RoleHierarchy roleHierarchy() {
803+
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
804+
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
805+
return roleHierarchy;
806+
}
807+
808+
}
809+
773810
@Configuration
774811
@EnableWebSecurity
775812
static class RoleUserOrAdminConfig {

0 commit comments

Comments
 (0)