Spring Security Role Hierarchy 无法使用 Java Config

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29888458/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 16:05:09  来源:igfitidea点击:

Spring Security Role Hierarchy not working using Java Config

javaspringspring-mvcspring-security

提问by mingchuno

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 the codes I have tried.

首先,我是 Java Spring Framework 的新手。所以如果我没有提供足够的信息,请原谅我。我曾尝试将 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;
  }

}


And I have a UserDetailsServiceImpl implements UserDetailsServicethat provide the principal, Authenticationand GrantedAuthority

我有一个UserDetailsServiceImpl implements UserDetailsService提供principal,AuthenticationGrantedAuthority

Finally I have some APIs:

最后我有一些 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)


The problem is now if I login as ROLE_STAFF, ROLE_DEVELOPER, ROLE_ADMIN, I got the following result.

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

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

As you can see ROLE_STAFFand ROLE_DEVELOPERwork just fine. But I want ROLE_ADMINas a super role of both and it didn't work.

正如你所看到的ROLE_STAFFROLE_DEVELOPER工作得很好。但我想ROLE_ADMIN同时扮演两者的超级角色,但没有奏效。

FYI, I am using spring-security 3.2.5.RELEASE

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

回答by mingchuno

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

问题出在 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;
}

keep calling setHierarchy()will override the setting before

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

回答by Lord Nighton

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

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

  1. We have to add a RoleHierarchyImplbean into context (You see, that I use multiple rolesto 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());
            }
    
  1. 我们必须在上下文中添加一个RoleHierarchyImplbean(你看,我使用多个角色来构建层次结构):

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

Result:in this particular example we try to visit /dbasection 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.

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

回答by David Rz Ayala

Override the createExpressionHandlermethod so it returns a configured global expression handler

覆盖该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 {}
      }
  }

回答by Vitaliy Polchuk

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

对我来说,解决方案是为DefaultWebSecurityExpressionHandler. 名字应该是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())
            ...
}

回答by user2210419

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

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

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):

定义 Authenticated 角色(普通用户角色):

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:

在我的 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

当局

In controllers:

在控制器中:

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

Will allow user logged in as ROLE_AUTHENTICATED and ROLE_ADMINISTRATOR both.

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