'Custom RoleHierarchy not working with Method Security in Spring Boot Web Application

I am creating a custom role hierarchy and trying to use it with both web based security and method level security. This is a Spring Boot Web Application.

My configuration files :

@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled=true,prePostEnabled=true)
public class Application {

  public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
  }
}

SecurityConfig :

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailsService userDetailsService;

@Autowired
ApplicationContext applicationContext;

@Override
public void configure(WebSecurity web) throws Exception {
    web
        .ignoring()
            .antMatchers("/resources/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
        .formLogin()
            .loginPage("/signin.html")
            .loginProcessingUrl("/signin/authenticate")
            .failureUrl("/signin?param.error=bad_credentials")
        .and()
            .logout()
                .logoutUrl("/signout")
                .deleteCookies("JSESSIONID")
        .and()
            .authorizeRequests()
                .expressionHandler(webExpressionHandler())
                .antMatchers("/signin**","/user/**","/admin/**", "/favicon.ico", "/resources/**", "/auth/**", "/signin/**", "/signup/**").permitAll()
                .antMatchers("/doc/**").hasRole("MOD").anyRequest().authenticated()
        .and()
            .apply(springSocialConfigurer())        
        .and()
            .rememberMe();
}
//Some more code for Spring Social integration
@Bean
public RoleHierarchy roleHierarchy() {
    RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
    roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_MOD > ROLE_USER > ROLE_GUEST");
    return roleHierarchy;
}

private SecurityExpressionHandler<FilterInvocation> webExpressionHandler() {
    DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
    defaultWebSecurityExpressionHandler.setApplicationContext(applicationContext);
    defaultWebSecurityExpressionHandler.setRoleHierarchy(roleHierarchy());
    return defaultWebSecurityExpressionHandler;
}

@Bean
public RoleHierarchyVoter roleHierarchyVoter() {
  return new RoleHierarchyVoter(roleHierarchy());
}

GlobalMethodSecurityConfig

@Configuration
public class GlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration {

@Autowired
private RoleHierarchy roleHierarchy;
@Autowired
ApplicationContext applicationContext; 

@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
    System.out.println("Method Security configured");
    DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
    expressionHandler.setApplicationContext(applicationContext);
    expressionHandler.setRoleHierarchy(roleHierarchy);
    return expressionHandler;
}

}

Now, I noticed the method createExpressionHandler() never gets called. That is probably because I have put the annotation @EnableGlobalMethodSecurity in the Application class ? If I put the annotation on the GlobalMethodSecurityConfig class, then the method gets called, but I get an exception when I run the program, which says :

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'defaultServletHandlerMapping' defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.HandlerMapping]: Factory method 'defaultServletHandlerMapping' threw exception; nested exception is java.lang.IllegalArgumentException: A ServletContext is required to configure default servlet handling

I am unable to figure out what is it that I am doing wrong here. Please help.

EDIT 1-------------

Okay, I think I have some idea, what is going on. Spring Security filters need to be configured quite early. So, probably moving the creation of RoleHierarchy Bean to a separate Configuration file will solve the problem. I just did that and I'm able to compile and run the code successfully.I have autowired RoleHierarchy in the other Configuration classes. The custom role, ROLE_MOD works too. However, the issue is not solved yet.

Since ROLE_MOD is above ROLE_USER in the hierarchy, shouldn't a user having ROLE_MOD be eligible to access a method having access for a ROLE_USER ?

@PreAuthorize("hasRole('ROLE_USER')")

This does not work for a user with ROLE_MOD, and I get an AccessDeniedException. I wonder if this is the correct way of configuring custom role-hierarchy at all. Please help me.

EDIT 2-------------

Okay, I made a little change to the way I was defining the custom role hierarchy. I changed it to :

roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_MOD and ROLE_MOD > ROLE_USER and ROLE_USER > ROLE_GUEST");

Now everything works! Thanks for giving me a space for thinking out loud!



Solution 1:[1]

Using spring boot, I just injected a RoleHierarchy and things work fine. No configuration made. Just injected a bean of type RoleHierarchy.

@Component
public class CustomRoleHierarchy {
    @Bean
    public RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF > ROLE_USER");
        return roleHierarchy;
    }
}

My config (nothing about roleHierarchy)

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf(AbstractHttpConfigurer::disable)
            .authorizeRequests(authorize -> authorize
                    .antMatchers(HttpMethod.POST, "/api/v1/tokens").permitAll()
                    .anyRequest().authenticated()
            ).oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
                .oauth2ResourceServer()
                .jwt()
                .jwtAuthenticationConverter(jwtAuthenticationConverter());
    }

    private JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("role");
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 humbleCraftman