【问题标题】:Spring Security Role Hierarchy not working using Java ConfigSpring Security Role Hierarchy 无法使用 Java Config
【发布时间】:2015-07-05 11:25:44
【问题描述】:

首先,我是 Java Spring 框架的新手。如果我没有提供足够的信息,请原谅我。我试图将 RoleHierarchy 添加到我的应用程序中,但它不起作用。以下是我尝试过的代码。


SecurityConfig.java

// These config is try to set up a user Role Hierarchy
@Bean
public RoleHierarchy roleHierarchy() {
  System.out.println("arrive public RoleHierarchy roleHierarchy()");
  RoleHierarchyImpl r = new RoleHierarchyImpl();
  r.setHierarchy("ROLE_ADMIN > ROLE_STAFF");
  r.setHierarchy("ROLE_STAFF > ROLE_USER");
  r.setHierarchy("ROLE_DEVELOPER > ROLE_USER");
  r.setHierarchy("ROLE_USER > ROLE_GUEST"); 
  return r;
}

@Bean
public AffirmativeBased defaultAccessDecisionManager(RoleHierarchy roleHierarchy){
  System.out.println("arrive public AffirmativeBased defaultAccessDecisionManager()");
  List<AccessDecisionVoter> decisionVoters = new ArrayList<>();

  // webExpressionVoter
  WebExpressionVoter webExpressionVoter = new WebExpressionVoter();
  DefaultWebSecurityExpressionHandler
      expressionHandler = new DefaultWebSecurityExpressionHandler();
  expressionHandler.setRoleHierarchy(roleHierarchy);
  webExpressionVoter.setExpressionHandler(expressionHandler);

  decisionVoters.add(webExpressionVoter);
  decisionVoters.add(roleHierarchyVoter(roleHierarchy));
  // return new AffirmativeBased(Arrays.asList((AccessDecisionVoter) webExpressionVoter));
  return new AffirmativeBased(decisionVoters);
}

@Bean
public RoleHierarchyVoter roleHierarchyVoter(RoleHierarchy roleHierarchy) {
  System.out.println("arrive public RoleHierarchyVoter roleHierarchyVoter");
  return new RoleHierarchyVoter(roleHierarchy);
}

@Override
protected void configure(HttpSecurity http) throws Exception {
  // skipping some codes
  http
    // skipping some codes
    .accessDecisionManager(defaultAccessDecisionManager(roleHierarchy()))
  // skipping some codes
}

MethodSecurityConfig.java

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {

  @Inject
  private SecurityConfig securityConfig;

  @Override
  protected AuthenticationManager authenticationManager() throws Exception {
    return securityConfig.authenticationManagerBean();
  }

  @Override
  protected MethodSecurityExpressionHandler createExpressionHandler() {
    System.out.println("arrive protected MethodSecurityExpressionHandler createExpressionHandler()");
    DefaultMethodSecurityExpressionHandler d = new DefaultMethodSecurityExpressionHandler();
    d.setRoleHierarchy(securityConfig.roleHierarchy());
    return d;
  }

}

我有一个UserDetailsServiceImpl implements UserDetailsService 提供principalAuthenticationGrantedAuthority

我终于有了一些 API:

@PreAuthorize("hasRole('ROLE_STAFF')")
@RequestMapping(value = "/api/v1/contactUs", method = RequestMethod.GET)

@PreAuthorize("hasRole('ROLE_DEVELOPER')")
@RequestMapping(value = "/api/v1/system", method = RequestMethod.GET)

现在的问题是,如果我以 ROLE_STAFF、ROLE_DEVELOPER、ROLE_ADMIN 身份登录,我会得到以下结果。

| API       | ROLE_STAFF | ROLE_DEVELOPER | ROLE_ADMIN |
|-----------|------------|----------------|------------|
| contactUs | 200        | 403            | 403        |
| system    | 403        | 200            | 403        |

如您所见,ROLE_STAFFROLE_DEVELOPER 工作正常。但我希望ROLE_ADMIN 成为两者的超级角色,但它不起作用。

仅供参考,我正在使用 spring-security 3.2.5.RELEASE

【问题讨论】:

    标签: java spring spring-mvc spring-security


    【解决方案1】:

    问题出在 RoleHierachy 中,应该是这样的:

    @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;
    }
    

    继续调用setHierarchy()会覆盖之前的设置

    【讨论】:

    • 请注意,使用“and”是可选的。对于 Spring,""ROLE_ADMIN > ROLE_STAFF and ROLE_ADMIN" 等价于 ""ROLE_ADMIN > ROLE_STAFF ROLE_ADMIN"。我更喜欢你的符号,但只是说
    • 这实际上是有效的,即使它使用可选的语法。如果它抛出某种语法异常会很好,因为我有A &gt; B &gt; C
    • 对 Spring-security 5.6.1 使用 "..and.." 不适用于男性。应该是A &gt; B &gt; C
    【解决方案2】:

    每当我想使用 Spring Security 和 Java 配置实现角色层次结构时,我都会使用以下方法:

    1. 我们必须在上下文中添加一个 RoleHierarchyImpl bean(你看,我使用 多个角色 来构建层次结构):

      @Bean
      public RoleHierarchyImpl roleHierarchy() {
          RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
          roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_DBA ROLE_DBA > ROLE_USER ");
          return roleHierarchy;
      }
      
    2. 然后我们需要创建 web 表达式处理程序来将获得的层次结构传递给它:

      private SecurityExpressionHandler<FilterInvocation> webExpressionHandler() {
          DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
          defaultWebSecurityExpressionHandler.setRoleHierarchy(roleHierarchy());
          return defaultWebSecurityExpressionHandler;
      }
      
    3. 最后一步是将 expressionHandler 添加到 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());
              }
      

    结果:在这个特定示例中,我们尝试在使用管理员用户 (ROLE_ADMIN) 登录后访问 /dba 部分。在我们创建层次结构之前,我们有一个拒绝访问的结果,但现在我们可以毫无问题地访问此部分。

    【讨论】:

    • 感谢您的回答 - 这比我在 google 上搜索时得到的任何一半尝试要好得多。另请注意,为此,您需要授予ROLE_ADMIN 的权限,而不是像我试图做的那样只是ADMIN
    【解决方案3】:

    注意:接受的答案在最新版本的 Spring 安全性中不起作用(我认为自 5.2.1 版起)。 这是因为'and' (ROLE_1 > ROLE_2 and ROLE_2 > ROLE_3) 符号从来都不是官方标准。您可以写下每个单词而不是“和”,并且在过去的版本中仍然可以使用。

    相反,在新版本中,您现在应该使用 '\n'(新行),例如ROLE_1 > ROLE_2\nROLE2 > ROLE_3 ...

    【讨论】:

      【解决方案4】:

      对我来说,解决方案是为 DefaultWebSecurityExpressionHandler 的实例设置正确的 bean 名称。名称应为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())
                  ...
      }
      

      【讨论】:

        【解决方案5】:

        覆盖createExpressionHandler 方法,使其返回配置的全局表达式处理程序

          @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 {}
              }
          }
        

        【讨论】:

          【解决方案6】:

          正确的格式是:ROLE_A &gt; ROLE_B and ROLE_B &gt; ROLE_C.doc

          @Bean
          public RoleHierarchy roleHierarchy() {
              RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
              roleHierarchy.setHierarchy(UserRole.ROLE_HIERARCHY);
              return roleHierarchy;
          }
          

          【讨论】:

            【解决方案7】:

            我不使用 Spring RoleHierarchy - 因为它对我不起作用。 但通常我喜欢这样: 定义角色接口

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

            我的角色列表(存储在数据库中):

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

            将匿名角色定义为基本角色类:

            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(普通用户角色):

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

            定义管理员角色(在进化的顶端):

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

            可选 - 静态工厂类:

            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");
              }
            }
            

            在我的 CustomUserDetailsS​​ervice(实现 UserDetailsS​​ervice)中,我使用这样的角色:

            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

            在控制器中:

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

            将允许用户以 ROLE_AUTHENTICATED 和 ROLE_ADMINISTRATOR 身份登录。

            【讨论】:

              猜你喜欢
              • 2015-04-20
              • 1970-01-01
              • 2017-03-03
              • 1970-01-01
              • 2016-09-24
              • 2014-01-11
              • 2012-04-18
              • 2014-12-27
              • 1970-01-01
              相关资源
              最近更新 更多