Spring Security Role Hierarchy not working using Java Config

前端 未结 6 2083
轻奢々
轻奢々 2021-01-04 21:48

First of all, I am new to Java Spring Framework. So forgive me if I did not provide enough info. I have tried to add RoleHierarchy into my app but it did not work. Below are

相关标签:
6条回答
  • 2021-01-04 21:53

    Everytime I want to implement a hierarchy of roles with Spring Security and Java config, I use the following approach:

    1. We have to add a RoleHierarchyImpl bean into context (You see, that I use multiple roles to build a hierarchy):

      @Bean
      public RoleHierarchyImpl roleHierarchy() {
          RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
          roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_DBA ROLE_DBA > ROLE_USER ");
          return roleHierarchy;
      }
      
    2. Then we need to create web expression handler to pass obtained hierarchy to it:

      private SecurityExpressionHandler<FilterInvocation> webExpressionHandler() {
          DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
          defaultWebSecurityExpressionHandler.setRoleHierarchy(roleHierarchy());
          return defaultWebSecurityExpressionHandler;
      }
      
    3. The final step is to add expressionHandler into http.authorizeRequests():

              @Override
              protected void configure(HttpSecurity http) throws Exception {
                  http
                     .authorizeRequests()
                          .expressionHandler(webExpressionHandler())
                          .antMatchers("/admin/**").access("(hasRole('ROLE_ADMIN') or hasRole('ROLE_DBA')) and isFullyAuthenticated()")
                          .antMatchers("/dba").access("hasRole('ROLE_DBA') and isFullyAuthenticated()")
                          .antMatchers("/dba/**").access("hasRole('ROLE_USER')")
                          .and()
                     .requiresChannel()
                          .antMatchers("/security/**").requiresSecure()
                          .anyRequest().requiresInsecure()
                          .and()
                     .formLogin()
                          .loginPage("/login")
                          .failureUrl("/login?auth=fail")
                          .usernameParameter("username")
                          .passwordParameter("password")
                          .defaultSuccessUrl("/admin")
                          .permitAll()
                          .and()
                     .logout()
                              .logoutUrl("/logout")
                              .deleteCookies("remember-me")
                              .invalidateHttpSession(true)
                              .logoutSuccessUrl("/index")
                              .permitAll()
                              .and()
                     .csrf()
                              .and()
                     .rememberMe().tokenValiditySeconds(1209600)
                              .and()
                     .exceptionHandling().accessDeniedPage("/403")
                              .and()
                     .anonymous().disable()
                     .addFilter(switchUserFilter());
              }
      

    Result: in this particular example we try to visit /dba section after we have logged in using admin user (ROLE_ADMIN). Before we created a hierarchy, we had an access denied result, but now we can visit this section without any problems.

    0 讨论(0)
  • 2021-01-04 21:55

    I don't use Spring RoleHierarchy - because it doea not work for me. But Ussualy I do like this: Define Role interface

    public static interface Role {
      String getName();
      List<String> getHierarchy();
    }
    

    List of my roles (to store in DB):

    public interface AuthStates {
      // Spring security works fine only with ROLE_*** prefix
      String ANONYMOUS = "ROLE_ANONYMOUS";
      String AUTHENTICATED = "ROLE_AUTHENTICATED";
      String ADMINISTRATOR = "ROLE_ADMINISTRATOR";
    }
    

    Define Anonymous role as basic role class:

    public static class Anonymous implements Role {
      private final String name;
      private final List<String> hierarchy = Lists.newArrayList(ANONYMOUS);
    
      public Anonymous() {
        this(ANONYMOUS);
      }
    
      protected Anonymous(String name) {
        this.name = name;
      }
    
      @Override
      public String getName() {
        return name;
      }
    
      @Override
      public List<String> getHierarchy() {
        return hierarchy;
      }
    
      protected void addHierarchy(String name) {
        hierarchy.add(name);
      }
    }
    

    Define Authenticated role (common user role):

    public static class Authenticated extends Anonymous {
      public Authenticated() {
        this(AUTHENTICATED);
      }
    
      protected Authenticated(String name) {
        super(name);
        addHierarchy(AUTHENTICATED);
      }
    }
    

    Define Administrator role (at the top of the evolution):

    public static class Administrator extends Authenticated {
      public Administrator() {
        this(ADMINISTRATOR);
      }
    
      protected Administrator(String name) {
        super(name);
        addHierarchy(ADMINISTRATOR);
      }
    }
    

    Optional - static factory class:

    public static Role getRole(String authState) {
      switch (authState) {
        case ANONYMOUS: return new Anonymous();
        case AUTHENTICATED: return new Authenticated();
        case ADMINISTRATOR: return new Administrator();
        default: throw new IllegalArgumentException("Wrong auth state");
      }
    }
    

    In my CustomUserDetailsService (which implements UserDetailsService) I use role like this:

    private Collection<GrantedAuthority> createAuthority(User user) {
      final List<GrantedAuthority> authorities = new ArrayList<>();
      AuthStates.Role userAuthState = AuthStates.getRole(user.getAuthState());
      for (String role : userAuthState.getHierarchy()) {
        authorities.add(new SimpleGrantedAuthority(role));
      }
      return authorities;
    }
    

    authorities

    In controllers:

    @PreAuthorize("hasRole('ROLE_AUTHENTICATED')")
    

    Will allow user logged in as ROLE_AUTHENTICATED and ROLE_ADMINISTRATOR both.

    0 讨论(0)
  • 2021-01-04 22:00

    Note: The accepted answer won't work in the newest version of Spring security (I think since release 5.2.1). This is because the 'and' (ROLE_1 > ROLE_2 and ROLE_2 > ROLE_3) notation was never an official standard. You could have written every word instead of 'and' and it would still work the same in the past versions.

    Instead, in the new version you should now use '\n' (new line), e.g. ROLE_1 > ROLE_2\nROLE2 > ROLE_3 ...

    0 讨论(0)
  • 2021-01-04 22:03

    The issue is in the RoleHierachy, which should be like this:

    @Bean
    public RoleHierarchy roleHierarchy() {
      RoleHierarchyImpl r = new RoleHierarchyImpl();
      r.setHierarchy("ROLE_ADMIN > ROLE_STAFF and ROLE_ADMIN > ROLE_DEVELOPER and ROLE_STAFF > ROLE_USER and ROLE_DEVELOPER > ROLE_USER");
      return r;
    }
    

    keep calling setHierarchy() will override the setting before

    0 讨论(0)
  • 2021-01-04 22:12

    Override the createExpressionHandler method so it returns a configured global expression handler

      @Configuration
      @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
      public class GlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
          @Autowired
          private RoleHierarchy roleHierarchy;
    
          @Override
          protected MethodSecurityExpressionHandler createExpressionHandler(){
              return methodSecurityExpressionHandler();
          }
    
          private DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(){
              DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
              expressionHandler.setRoleHierarchy(roleHierarchy);
              return expressionHandler;
          }
    
          @Bean
          public RoleHierarchyImpl roleHierarchy() {
              RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
          roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_OWNER > ROLE_USER");
              return roleHierarchy;
          }
    
          @Bean
          public RoleHierarchyVoter roleVoter() {
              return new RoleHierarchyVoter(roleHierarchy);
          }
    
          @Configuration
          public static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
              @Override
              protected void configure(HttpSecurity http) throws Exception {}
          }
      }
    
    0 讨论(0)
  • 2021-01-04 22:13

    For me the solution was having proper bean name for the instance of DefaultWebSecurityExpressionHandler. The name should be webSecurityExpressionHandler.

    @Bean
    public RoleHierarchyImpl roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy(Roles.getRoleHierarchy());
        return roleHierarchy;
    }
    
    @Bean
    public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler() {
        DefaultWebSecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();
        expressionHandler.setRoleHierarchy(roleHierarchy());
        return expressionHandler;
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .expressionHandler(webSecurityExpressionHandler())
                ...
    }
    
    0 讨论(0)
提交回复
热议问题