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.
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.
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)
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.
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.
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.
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.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With