0

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.

Paul
  • 19,704
  • 14
  • 78
  • 96
  • This is not an exact answer to you question, but it is an approach that works for me since years: http://stackoverflow.com/a/22612076/280244 – Ralph Dec 16 '14 at 20:07

2 Answers2

1

I'd override GlobalMethodSecurityConfiguration#accessDecisionManager method. You can see source code that RoleVoter uses.

Here is my suggested overridden source code.

    @Override
    protected AccessDecisionManager accessDecisionManager() {
        var roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy("ROLE_SUPER > ROLE_ADMIN");
        
        var expressionHandler = (DefaultMethodSecurityExpressionHandler) getExpressionHandler();
        expressionHandler.setRoleHierarchy(roleHierarchy);

        var expressionAdvice = new ExpressionBasedPreInvocationAdvice();
        expressionAdvice.setExpressionHandler(expressionHandler);

        return new AffirmativeBased(List.of(
                new RoleHierarchyVoter(roleHierarchy),
                new PreInvocationAuthorizationAdviceVoter(expressionAdvice),
                new AuthenticatedVoter(),
                new Jsr250Voter()
        ));
    }
hurelhuyag
  • 1,691
  • 1
  • 15
  • 20
-1

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.

Paul
  • 19,704
  • 14
  • 78
  • 96