Issue
Using Spring Security 3.2.5 and Spring 4.1.2, 100% Java config
Our webapp has global method security enabled and service methods annotated with @PreAuthorize
- everything is working as expected. I'm trying to add a role hierarchy and having no success at all. Here's the hierarchy I'm trying to achieve:
- ROLE_ADMIN can access all methods that ROLE_USER can access.
- ROLE_USER can access all methods that ROLE_DEFAULT can access.
Despite my best efforts, a user with ROLE_ADMIN receives a 403 when doing something that results in a call to a method annotated with @PreAuthorized("hasAuthority('ROLE_DEFAULT')")
Here's the relevant configuration code:
AppInitializer
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
{
@Override
protected Class<?>[] getRootConfigClasses()
{
return new Class[]
{
AppConfig.class, SecurityConfig.class
};
}
@Override
protected Class<?>[] getServletConfigClasses()
{
return new Class[]
{
MvcConfig.class
};
}
// other methods not shown for brevity
}
AppConfig.java
@Configuration
@ComponentScan(basePackages={"myapp.config.profile", "myapp.dao", "myapp.service", "myapp.security"})
public class AppConfig
{
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth,
AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> detailSvc) throws Exception
{
PreAuthenticatedAuthenticationProvider authProvider = new PreAuthenticatedAuthenticationProvider();
authProvider.setPreAuthenticatedUserDetailsService(detailSvc);
auth.authenticationProvider(authProvider);
}
// other methods not shown for brevity
}
SecurityConfig.java
@Configuration
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
@Override
protected void configure(HttpSecurity http) throws Exception
{
PKIAuthenticationFilter pkiFilter = new PKIAuthenticationFilter();
pkiFilter.setAuthenticationManager(authenticationManagerBean());
http.authorizeRequests()
.antMatchers("/app/**").fullyAuthenticated()
.and()
.anonymous().disable()
.jee().disable()
.formLogin().disable()
.csrf().disable()
.x509().disable()
.addFilter(pkiFilter)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Override
public void configure(WebSecurity web) throws Exception
{
// ignore everything but /app/*
web.ignoring().regexMatchers("^(?!/app/).*");
}
}
MvcConfig.java
@Configuration
@EnableWebMvc
@ComponentScan({"myapp.controller"})
public class MvcConfig extends WebMvcConfigurerAdapter
{
// resource handlers, content negotiation, message converters configured here
}
In the same package as SecurityConfig
(so it is thus part of the AppConfig
component scan) I had this class:
GlobalMethodSecurityConfig.java
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class GlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration
{
@Bean
public RoleHierarchy roleHierarchy()
{
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER > ROLE_DEFAULT");
return roleHierarchy;
}
@Bean
public RoleVoter roleVoter()
{
return new RoleHierarchyVoter(roleHierarchy);
}
@Bean
@Override
protected AccessDecisionManager accessDecisionManager()
{
return new AffirmativeBased(Arrays.asList(roleVoter()));
}
// The method below was added in an attempt to get things working but it is never called
@Override
protected MethodSecurityExpressionHandler createExpressionHandler()
{
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
handler.setRoleHierarchy(roleHierarchy());
return handler;
}
}
In another attempt I made AppConfig
extend GlobalMethodSecurityConfiguration
but a user with ROLE_ADMIN cannot call a method requiring ROLE_DEFAULT access.
I'm sure I've misconfigured something somewhere but I can't figure out where I've gone wrong despite reading everything I can find on configuring global method security with a role hierarchy. It appears this would be trivial using XML configuration but the Java config solution eludes me.
Solution
Since this question keeps getting views I thought I'd post a follow-up to it. The problem appears to be with the line
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER > ROLE_DEFAULT");
I don't remember why I wrote the hierarchy like that but it's not correct. The API for that method handles the same situation thusly:
Role hierarchy: ROLE_A > ROLE_B and ROLE_B > ROLE_C.
Directly assigned authority: ROLE_A.
Reachable authorities: ROLE_A, ROLE_B, ROLE_C.
Eventually it became clear that a hierarchical model didn't fit our roles so we instead implemented a finer-grained set of authorities mapped to roles, as mentioned in the Spring Security Reference:
For more complex requirements you may wish to define a logical mapping between the specific access-rights your application requires and the roles that are assigned to users, translating between the two when loading the user information.
Answered By - Paul
Answer Checked By - Timothy Miller (JavaFixing Admin)