Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 434
Mario Avatar asked Oct 30 '14 18:10

Mario


People also ask

What are roles in Spring Security?

The Role represents the high-level roles of the user in the system. Each role will have a set of low-level privileges. The Privilege represents a low-level, granular privilege/authority in the system.

What is hasRole and hasAnyRole?

Description. hasRole([role]) Returns true if the current principal has the specified role. hasAnyRole([role1,role2]) Returns true if the current principal has any of the supplied roles (given as a comma-separated list of strings)

Which class in Spring Security framework is used to define role?

The UserDetailsService is a core interface in Spring Security framework, which is used to retrieve the user's authentication and authorization information. This interface is also responsible to provide the User's GrantedAuthority list, which is used to derive our spring security roles and permissions for the user.


2 Answers

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.

like image 182
Luan Nguyen Avatar answered Oct 02 '22 15:10

Luan Nguyen


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;
}
like image 40
Himank Batra Avatar answered Oct 02 '22 15:10

Himank Batra