Skip to content

RoleHierarchy bean does not apply to AuthorityAuthorizationManager #13911

Closed
@bwgjoseph

Description

@bwgjoseph

Describe the bug

Declaring RoleHierarchy in a @Bean behave differently when declared manually to AuthorityAuthorizationManager.

I'm using spring-boot-3.1.4

To Reproduce

Please refer to my repo for full example.

I have the following security configuration

@EnableMethodSecurity
@EnableWebSecurity(debug = false)
@Configuration(proxyBeanMethods = false)
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
            .formLogin(AbstractHttpConfigurer::disable)
            .httpBasic(AbstractHttpConfigurer::disable)
            .anonymous(AbstractHttpConfigurer::disable)
            .csrf(AbstractHttpConfigurer::disable)
            .logout(AbstractHttpConfigurer::disable)
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(httpReq -> httpReq.anyRequest().access(haveReadPermission()))
            .build();
    }

    // see below for the configuration for RoleHierarchy and AuthorityAuthorizationManager
}

And the following test

@WebMvcTest(MeController.class)
@Import({ SecurityConfig.class})
class MeControllerTests {
    @Autowired
    private MockMvc mockMvc;

    @Test
    @WithMockUser(roles = { "ADMIN" }, authorities = { "ROLE_ADMIN" } )
    void test1() throws Exception {
        this.mockMvc
            .perform(MockMvcRequestBuilders
                .get("/me"))
            .andDo(MockMvcResultHandlers.print());
    }
}

Pass Scenario

Given the following configuration; manually passing in role hierarchy, the test will pass

public RoleHierarchy roleHierarchy() {
    RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();

    String roleHierarchyFromMap = """
            ROLE_ADMIN > ROLE_STAFF
            ROLE_STAFF > ROLE_USER
            ROLE_STAFF > ROLE_GUEST
            """;

    roleHierarchy.setHierarchy(roleHierarchyFromMap);
    return roleHierarchy;
}

private AuthorizationManager<RequestAuthorizationContext> haveReadPermission() {
    AuthorityAuthorizationManager<RequestAuthorizationContext> authority = AuthorityAuthorizationManager.hasAnyAuthority("ROLE_USER");
    authority.setRoleHierarchy(this.roleHierarchy());
    return authority;
}
click to view test result
MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /me
       Parameters = {}
          Headers = []
             Body = null
    Session Attrs = {}

Handler:
             Type = com.bwgjoseph.springsecurityrolehierarchybug.MeController
           Method = com.bwgjoseph.springsecurityrolehierarchybug.MeController#me(MyUserDetails)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"application/json", X-Content-Type-Options:"nosniff", X-XSS-Protection:"0", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = application/json
             Body = {"username":"rob","email":"[email protected]","roles":["ROLE_ADMIN"],"authorities":[{"authority":"ROLE_ADMIN"}],"enabled":true,"password":"N/A","credentialsNonExpired":true,"accountNonExpired":true,"accountNonLocked":true}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

Fail Scenario

With the following configuration; configure RoleHierarchy as @Bean and remove manually passing in role hierarchy. The test will fail.

@Bean // enable this
public RoleHierarchy roleHierarchy() {
    RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();

    String roleHierarchyFromMap = """
            ROLE_ADMIN > ROLE_STAFF
            ROLE_STAFF > ROLE_USER
            ROLE_STAFF > ROLE_GUEST
            """;

    roleHierarchy.setHierarchy(roleHierarchyFromMap);
    return roleHierarchy;
}

private AuthorizationManager<RequestAuthorizationContext> haveReadPermission() {
    AuthorityAuthorizationManager<RequestAuthorizationContext> authority = AuthorityAuthorizationManager.hasAnyAuthority("ROLE_USER");
    // authority.setRoleHierarchy(this.roleHierarchy()); // remove this
    return authority;
}
click to view test result
MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /me
       Parameters = {}
          Headers = []
             Body = null
    Session Attrs = {}

Handler:
             Type = null

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 403
    Error message = Forbidden
          Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"0", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

Expected behavior

I expect that AuthorityAuthorizationManager should have the same behavior whether RoleHierarchy is defined as a @Bean or manually through the setter.

This issue - #13188 - seem to suggest that this feature should be in working state after spring-boot 3.1.0

Possible related issue

Sample

Click the link to a GitHub repository with a minimal, reproducible sample.

Metadata

Metadata

Labels

status: invalidAn issue that we don't feel is validtype: bugA general bug

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions