'Custom RoleHierarchy not working with Method Security in Spring Boot Web Application
I am creating a custom role hierarchy and trying to use it with both web based security and method level security. This is a Spring Boot Web Application.
My configuration files :
@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled=true,prePostEnabled=true)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
SecurityConfig :
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
ApplicationContext applicationContext;
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.formLogin()
.loginPage("/signin.html")
.loginProcessingUrl("/signin/authenticate")
.failureUrl("/signin?param.error=bad_credentials")
.and()
.logout()
.logoutUrl("/signout")
.deleteCookies("JSESSIONID")
.and()
.authorizeRequests()
.expressionHandler(webExpressionHandler())
.antMatchers("/signin**","/user/**","/admin/**", "/favicon.ico", "/resources/**", "/auth/**", "/signin/**", "/signup/**").permitAll()
.antMatchers("/doc/**").hasRole("MOD").anyRequest().authenticated()
.and()
.apply(springSocialConfigurer())
.and()
.rememberMe();
}
//Some more code for Spring Social integration
@Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_MOD > ROLE_USER > ROLE_GUEST");
return roleHierarchy;
}
private SecurityExpressionHandler<FilterInvocation> webExpressionHandler() {
DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
defaultWebSecurityExpressionHandler.setApplicationContext(applicationContext);
defaultWebSecurityExpressionHandler.setRoleHierarchy(roleHierarchy());
return defaultWebSecurityExpressionHandler;
}
@Bean
public RoleHierarchyVoter roleHierarchyVoter() {
return new RoleHierarchyVoter(roleHierarchy());
}
GlobalMethodSecurityConfig
@Configuration
public class GlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Autowired
private RoleHierarchy roleHierarchy;
@Autowired
ApplicationContext applicationContext;
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
System.out.println("Method Security configured");
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setApplicationContext(applicationContext);
expressionHandler.setRoleHierarchy(roleHierarchy);
return expressionHandler;
}
}
Now, I noticed the method createExpressionHandler() never gets called. That is probably because I have put the annotation @EnableGlobalMethodSecurity in the Application class ? If I put the annotation on the GlobalMethodSecurityConfig class, then the method gets called, but I get an exception when I run the program, which says :
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'defaultServletHandlerMapping' defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.HandlerMapping]: Factory method 'defaultServletHandlerMapping' threw exception; nested exception is java.lang.IllegalArgumentException: A ServletContext is required to configure default servlet handling
I am unable to figure out what is it that I am doing wrong here. Please help.
EDIT 1-------------
Okay, I think I have some idea, what is going on. Spring Security filters need to be configured quite early. So, probably moving the creation of RoleHierarchy Bean to a separate Configuration file will solve the problem. I just did that and I'm able to compile and run the code successfully.I have autowired RoleHierarchy in the other Configuration classes. The custom role, ROLE_MOD works too. However, the issue is not solved yet.
Since ROLE_MOD is above ROLE_USER in the hierarchy, shouldn't a user having ROLE_MOD be eligible to access a method having access for a ROLE_USER ?
@PreAuthorize("hasRole('ROLE_USER')")
This does not work for a user with ROLE_MOD, and I get an AccessDeniedException. I wonder if this is the correct way of configuring custom role-hierarchy at all. Please help me.
EDIT 2-------------
Okay, I made a little change to the way I was defining the custom role hierarchy. I changed it to :
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_MOD and ROLE_MOD > ROLE_USER and ROLE_USER > ROLE_GUEST");
Now everything works! Thanks for giving me a space for thinking out loud!
Solution 1:[1]
Using spring boot, I just injected a RoleHierarchy and things work fine. No configuration made. Just injected a bean of type RoleHierarchy.
@Component
public class CustomRoleHierarchy {
@Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF > ROLE_USER");
return roleHierarchy;
}
}
My config (nothing about roleHierarchy)
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeRequests(authorize -> authorize
.antMatchers(HttpMethod.POST, "/api/v1/tokens").permitAll()
.anyRequest().authenticated()
).oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter());
}
private JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("role");
jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | humbleCraftman |
