Roles hierarchy not working after upgrading to spring security 6
Asked Answered
N

5

3

I am upgrading from spring boot 2.7.x to 3.0.0. After doing changes as recommended in the official docs I found that my role hierarchies are not being honored.

I added expressionHandler() to my code as suggested in AccessDecisionVoter Deprecated with Spring Security 6.x but it doesn't work.

Any ideas what am I missing?

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

  @Bean
  public SecurityFilterChain configure(
      HttpSecurity http,
      RequestHeaderAuthenticationFilter headerAuthenticationFilter) throws Exception {
    
    HttpStatusEntryPoint authenticationEntryPoint = 
        new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED);
    
    http
        .addFilterAfter(headerAuthenticationFilter, RequestHeaderAuthenticationFilter.class)
        .authorizeHttpRequests(auth -> auth
          .requestMatchers("/actuator/**", "/", "/webjars/**").permitAll()
          .requestMatchers(HttpMethod.POST).hasRole("SUPERUSER")
          .requestMatchers(HttpMethod.GET).hasRole("USER"))
        .sessionManagement(session -> session
          .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .exceptionHandling(ex -> ex
          .authenticationEntryPoint(authenticationEntryPoint)
          .accessDeniedHandler(accessDeniedHandler()))
        .csrf(customizer -> customizer.disable());

    return http.build();
  }

  @Bean
  public RequestHeaderAuthenticationFilter headerAuthenticationFilter(
      ...
  }

  @Bean
  public RoleHierarchy roleHierarchy() {
    RoleHierarchyImpl r = new RoleHierarchyImpl();
    r.setHierarchy("ROLE_SUPERUSER > ROLE_USER");
    return r;
  }

  @Bean
  public DefaultWebSecurityExpressionHandler expressionHandler() {
    DefaultWebSecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();
    expressionHandler.setRoleHierarchy(roleHierarchy());
    return expressionHandler;
  }
Numerous answered 27/12, 2022 at 17:52 Comment(0)
N
1

AuthorityAuthorizationManager is not exposed as a bean. Indeed it is a final class with private constructor. So in order to use my role hierarchy I need to create manually the AuthorityAuthorizationManager.

This worked using spring boot 3.0.0 and spring security 6.0.0

  @Bean
  public SecurityFilterChain configure(
      HttpSecurity http,
      RequestHeaderAuthenticationFilter headerAuthenticationFilter) throws Exception {

    var auth1 = AuthorityAuthorizationManager.<RequestAuthorizationContext>hasRole("USER");
    auth1.setRoleHierarchy(roleHierarchy());
    
    http
        .authorizeHttpRequests(auth -> auth
          .requestMatchers(HttpMethod.GET).access(auth1)
        );
    return http.build();
  }

 @Bean
  public RoleHierarchy roleHierarchy() {
    RoleHierarchyImpl r = new RoleHierarchyImpl();
    r.setHierarchy("ROLE_SUPERUSER > ROLE_USER");
    return r;
  }
Numerous answered 28/12, 2022 at 15:49 Comment(2)
Did you come across any better alternative?Mothy
@Mothy Check this answer https://mcmap.net/q/1916060/-roles-hierarchy-not-working-after-upgrading-to-spring-security-6Exponent
D
1

The best way I found was to call into GrantedAuthoritiesMapper#mapAuthorities the same way AbstractUserDetailsAuthenticationProvider does it. Unfortunately PreAuthenticatedAuthenticationProvider does not call into it the same way so you will have to do it yourself.

This will make it work everywhere including method security and other places than HttpSecurity. It's probably the right way given most auth providers are doing it. Also it will avoid expanding hierarchy per every check.

  @Bean
  public GrantedAuthoritiesMapper grantedAuthoritiesMapper(RoleHierarchy roleHierarchy) {
      return new RoleHierarchyAuthoritiesMapper(roleHierarchy);
  }

  @Bean
  public RequestHeaderAuthenticationFilter headerAuthenticationFilter(GrantedAuthoritiesMapper mapper) {
      var filter = new RequestHeaderAuthenticationFilter();
      //...
      filter.setAuthenticationDetailsSource(context -> {
          //...
          return new PreAuthenticatedGranteduthoritiesWebAuthenticationDetails(context, mapper.mapAuthorities(authorities));
      });
      return filter;
  }

Duelist answered 23/2, 2023 at 21:4 Comment(0)
F
0

A also have a hierarchy - ROLE_EDIT > ROLE_READ. A solution from my small project after migration from Spring Boot 2 to Spring Boot 3 is:

@Bean
public AuthorizationManager<RequestAuthorizationContext> authorizationManager_READ(RoleHierarchy roleHierarchy) {
    return (authentication, object) -> {
        AuthorityAuthorizationManager<RequestAuthorizationContext> authorityAuthorizationManager = AuthorityAuthorizationManager.hasAnyAuthority("ROLE_READ");
        authorityAuthorizationManager.setRoleHierarchy(roleHierarchy);

        return authorityAuthorizationManager.check(authentication, object);
    };
}

in filterChain

.requestMatchers(HttpMethod.GET, "/internal-order/**").access(authorizationManager_READ)

Maybe not the best solution, but it works.

Fiddlewood answered 26/2, 2023 at 11:10 Comment(0)
E
0

From Spring Boot 3.0.3 onward, you can define RoleHierarchy as a @Bean and it will be determined by the AuthorizeHttpRequestsConfigurer and set to the AuthorityAuthorizationManager using its setter.

import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Bean
    public RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        String hierarchy = "ROLE_ADMIN > ROLE_MODERATOR \n ROLE_MODERATOR > ROLE_USER";
        roleHierarchy.setHierarchy(hierarchy);
        return roleHierarchy;
    }

    // ...
}

The actual issue was fixed in Spring Security 6.1.0-M1.

Exponent answered 13/3, 2023 at 22:10 Comment(2)
Thanks! But how is that answer different from the one given by David below?Mothy
In David's approach, the AuthorityAuthorizationManager is explicitly set for one specific RequestMatcher. However, here the RoleHierarchy is exposed globally and you can use this in any route without needing to set the access().Exponent
M
0

Since the current release of Spring Boot I am using is 3.0.5, the fix mentioned by bytesandcaffeine here https://mcmap.net/q/1916060/-roles-hierarchy-not-working-after-upgrading-to-spring-security-6 does not exist yet. So the simple solution I found is to create a DefaultMethodSecurityExpressionHandler bean and set my role hierarchy to it.

Morpho answered 31/3, 2023 at 13:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.