Reputation: 237
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
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
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
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.
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.
Upvotes: 14
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
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