Mario
Mario

Reputation: 237

Spring Boot + Spring Security + Hierarchical Roles

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.

Upvotes: 17

Views: 17588

Answers (5)

Himank Batra
Himank Batra

Reputation: 351

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;
}

Upvotes: 3

Sushil GC
Sushil GC

Reputation: 127

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.

Upvotes: 1

Luan Nguyen
Luan Nguyen

Reputation: 225

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.

Upvotes: 14

phlebas
phlebas

Reputation: 1210

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.

Upvotes: 3

holmis83
holmis83

Reputation: 16604

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)
    ...

Upvotes: 3

Related Questions