16

I'm trying to setup hierarchical roles in my Spring Boot app without success. I've done all that's been said in different places in the Internet. But with none of them have I been able to solve the issue.

Here is the code of my SecurityConfig class. When I login in the app with a user with ROLE_ADMIN it should be able to retrieve data from '/users', but currently I receive an Access Denied Exception. If the user has the ROLE_USER credential, it works fine. Can anyone help me figure it out what is failing? Thanks in advance.

@Configuration
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private SigpaUserDetailsService userDetailsService;

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

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

    @Bean 
    public DefaultWebSecurityExpressionHandler expressionHandler(){
        DefaultWebSecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();
        expressionHandler.setRoleHierarchy(roleHierarchy());
        return expressionHandler;
    }

    @Bean
    @SuppressWarnings(value = { "rawtypes" })
    public AffirmativeBased accessDecisionManager() {       
        List<AccessDecisionVoter> decisionVoters = new ArrayList<AccessDecisionVoter>();
        WebExpressionVoter webExpressionVoter = new WebExpressionVoter();
        webExpressionVoter.setExpressionHandler(expressionHandler());
        decisionVoters.add(webExpressionVoter);
        decisionVoters.add(roleVoter());
        return new AffirmativeBased(decisionVoters);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .accessDecisionManager(accessDecisionManager())
            .expressionHandler(expressionHandler())
            .antMatchers("/users/**")
                .access("hasRole('ROLE_USER')")
            .anyRequest().authenticated();
        http
            .formLogin()
                .loginPage("/login").permitAll()
                .and()
            .logout()
                .permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder registry)
            throws Exception {
        registry.userDetailsService(userDetailsService);
    }
}

Update: Here is the code updated with your suggestion, but still isn't working.

Mario
  • 227
  • 1
  • 2
  • 7

5 Answers5

13

I just went thru these setup so will definitely get you up running now. Here is the deal:

You brought in this annotation @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) but didn't show any code to use Pre/Post Authorize/Filter so I don't know if you actually need it.

  1. If you don't need that class/method level security/filtering then all you need to do is:

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

and

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

http
        .authorizeRequests()
        .expressionHandler(webExpressionHandler())

You don't have to override with your own accessDecisionManager if all you need is to introduce a role hierarchy.

  1. If you also need class/method level security, i.e. using PreAuthorize, PostAuthorize, PreFilter, PostFilter on your methods/classes then also create a @Configuration like this in your classpath (and remove the @EnableGlobalMethodSecurity annotation from your GlobalMethodSecurityConfig class):

    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled=true)
    public class AnyNameYouLike extends GlobalMethodSecurityConfiguration {
    
    @Resource
    private RoleHierarchy roleHierarchy;
    
    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        DefaultMethodSecurityExpressionHandler expressionHandler = (DefaultMethodSecurityExpressionHandler) super.createExpressionHandler();
        expressionHandler.setRoleHierarchy(roleHierarchy);
        return expressionHandler;
    }
    

    }

I would give the name GlobalMethodSecurityConfig to this new class and change your current GlobalMethodSecurityConfig class to WebSecurityConfig or something to reflect that it's the security setting for the web tier.

I define the RoleHierarchy bean in the webSecurityConfig and inject/use it in the globalMethodSecurityConfig, but you can do that any way you like, as long as you don't create 2 beans unnecessarily.

Hope this helps.

Luan Nguyen
  • 215
  • 3
  • 9
  • 1
    Hi, thanks for your answer! Unfortunately, Spring doesn't let me remove `@EnableGlobalMethodSecurity` annotation from my `WebSecurityConfig` class - I get `IllegalArgumentException: A ServletContext is required to configure default servlet handling`. Any help appreciated – kiedysktos Apr 14 '17 at 07:39
  • OK, I've found a solution. Looks like your advice in point 2. wasn't that good since for me it works the other way around. See an answer here: http://stackoverflow.com/questions/43468285/springboot-method-based-hierarchical-roles-security-servletcontext-is-require/43473615#43473615 – kiedysktos Apr 18 '17 at 13:30
  • I am not able to get this code running. When overriding the `GlobalMethodSecurityConfiguration` class just like in your example, I get an `A bean with that name has already been defined ..` error for the `methodSecurityInterceptor` bean. What am I doing wrong? – Jazzschmidt Apr 23 '19 at 15:49
3

You need to set the role hierarchy on the web expression voter. Something like:

DefaultWebSecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy);
webExpressionVoter.setExpressionHandler(expressionHandler);

Update: You could also try setting the the above expression handler like this:

http
    .authorizeRequests()
    .expressionHandler(expressionHandler)
    ...
holmis83
  • 15,922
  • 5
  • 82
  • 83
  • It doesn't work that way. I still receive the same Access Denied exception – Mario Oct 30 '14 at 21:53
  • I did that yesterday already... I have no idea why isn't working because I've done everything possible without any success. – Mario Oct 31 '14 at 12:04
  • After searching for hours on this `http. authorizeRequests().expressionHandler(expressionHandler)` worked – jax Aug 11 '17 at 05:16
3

You have to set the role hierarchy on the MethodSecurityExpressionHandler:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public static class GlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
    @Autowired
    private RoleHierarchy roleHierarchy;

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        final DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
        handler.setRoleHierarchy(this.roleHierarchy);
        return handler;
    }
}

Check Javadoc for @EnableGlobalMethodSecurity for further information. Especially notice: that EnableGlobalMethodSecurity still must be included on the class extending GlobalMethodSecurityConfiguration to determine the settings.

phlebas
  • 1,210
  • 2
  • 13
  • 24
3

We can clearly see setHierarchy method of RoleHierarchyImpl class. They are splitting it with "\n" for more than 2 hierarchy roles.

@Bean
public RoleHierarchyImpl roleHierarchy() {
    RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
    roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_WRITER\n" +
            "ROLE_ADMIN > ROLE_EDITOR");
    return roleHierarchy;
}
Himank Batra
  • 301
  • 5
  • 3
1

To Enable Method Level Security( ie @EnableGlobalMethodSecurity(prePostEnabled = true)) along with supporting Hierarchical-role on WebSecurityConfigurerAdapter.

1.Just need to seperate the RoleHierarchy on any other class annotated with @Bean
2.Inject it using @Autowired on WebSecurityConfigurerAdapter. It is working flawlessly on my projects.

Please have a look into my code.

WeSecurityConfig.class

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class AppSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private RoleHierarchy roleHierarchy;

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

    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
        web.ignoring().antMatchers("/static/**");
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.
            authorizeRequests()
            .expressionHandler(webExpressionHandler())
            .antMatchers("/static/**","/bower_components/**","/").permitAll()
            .antMatchers("/user/login","/user/login?error").anonymous()
            .anyRequest().authenticated()
            .and()
            .formLogin().loginPage("/user/login").passwordParameter("password").usernameParameter("username")
            .defaultSuccessUrl("/")
            .permitAll()
            .and()
            .logout().logoutUrl("/user/logout")
            .logoutSuccessUrl("/user/login?logout")
            .and().csrf();

    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(daoAuthenticationProvider());
    }

    public DaoAuthenticationProvider daoAuthenticationProvider(){
        final DaoAuthenticationProvider auth = new DaoAuthenticationProvider();
        auth.setUserDetailsService(userDetailService);
        auth.setPasswordEncoder(passwordEncoder);
        return auth;
    }
}

BeanConfiguration.class

@Configuration
public class BeanConfiguration {

    @Bean
    public RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        /* tricks lies here */
        roleHierarchy.setHierarchy("ROLE_SUPREME > ROLE_ADMIN ROLE_ADMIN > ROLE_OPERATOR ROLE_OPERATOR > ROLE_GUEST");
        return roleHierarchy;
    }
}

Hope It helps you.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
Sushil GC
  • 127
  • 1
  • 2
  • 11