史上最简单的Spring Security教程(十九):AccessDecisionVoter简介及自定义访问权限投票器

 

为了后续对 AccessDecisionManager 的介绍,我们先来提前对 AccessDecisionVoter 做个简单的了解,然后,在捎带手自定义一个 AccessDecisionVoter

AccessDecisionVoter 的注释介绍如下:

Indicates a class is responsible for voting on authorization decisions. The coordination of voting (ie polling {@code AccessDecisionVoter}s, tallying their responses, and making the final authorization decision) is performed by an {@link org.springframework.security.access.AccessDecisionManager}.

什么意思呢?

翻译过来就是,AccessDecisionVoter 是一个投票器,负责对授权决策进行表决。然后,最终由唱票者AccessDecisionManager 统计所有的投票器表决后,来做最终的授权决策。

先来看几种常用的投票器。

 

WebExpressionVoter

 

最常用的,也是 Spring Security 框架默认 FilterSecurityInterceptor 实例中 AccessDecisionManager 默认的投票器 WebExpressionVoter。其实,就是对使用 http.authorizeRequests() 基于 Spring-EL进行控制权限的的授权决策类。

http
    .authorizeRequests()
    .anyRequest()
    .authenticated()
    .antMatchers().permitAll()
    .antMatchers().hasRole()
    .antMatchers().hasAuthority()
    ......

 

他还需要一个 ExpressionHandler,感兴趣的话,可以去看一下文章史上最简单的Spring Security教程(十七):FilterSecurityInterceptor默认初始化逻辑剖析里面有对 WebExpressionVoter  ExpressionHandler 的相关介绍。

 

 

AuthenticatedVoter


针对 ConfigAttribute#getAttribute() 中配置为 IS_AUTHENTICATED_FULLY、IS_AUTHENTICATED_REMEMBERED、IS_AUTHENTICATED_ANONYMOUSLY 权限标识时的授权决策。因此,其投票策略比较简单:

public int vote(Authentication authentication, Object object,
      Collection attributes) {
    int result = ACCESS_ABSTAIN;
​
    for (ConfigAttribute attribute : attributes) {
      if (this.supports(attribute)) {
        result = ACCESS_DENIED;
​
        if (IS_AUTHENTICATED_FULLY.equals(attribute.getAttribute())) {
          if (isFullyAuthenticated(authentication)) {
            return ACCESS_GRANTED;
          }
        }
​
        if (IS_AUTHENTICATED_REMEMBERED.equals(attribute.getAttribute())) {
          if (authenticationTrustResolver.isRememberMe(authentication)
              || isFullyAuthenticated(authentication)) {
            return ACCESS_GRANTED;
          }
        }
​
        if (IS_AUTHENTICATED_ANONYMOUSLY.equals(attribute.getAttribute())) {
          if (authenticationTrustResolver.isAnonymous(authentication)
              || isFullyAuthenticated(authentication)
              || authenticationTrustResolver.isRememberMe(authentication)) {
            return ACCESS_GRANTED;
          }
        }
      }
    }
​
    return result;
  }
}

 

PreInvocationAuthorizationAdviceVoter

 

用于处理基于注解 @PreFilter 和 @PreAuthorize 生成的 PreInvocationAuthorizationAdvice,来处理授权决策的实现。还记得我们最早使用 @PreAuthorize 来进行权限控制的介绍吗?史上最简单的Spring Security教程(十二):@PreAuthorize注解实现权限控制。

public int vote(Authentication authentication, MethodInvocation method,
                Collection attributes) {
​
    // Find prefilter and preauth (or combined) attributes
    // if both null, abstain
    // else call advice with them
​
    PreInvocationAttribute preAttr = findPreInvocationAttribute(attributes);
​
    if (preAttr == null) {
        // No expression based metadata, so abstain
        return ACCESS_ABSTAIN;
    }
​
    boolean allowed = preAdvice.before(authentication, method, preAttr);
​
    return allowed ? ACCESS_GRANTED : ACCESS_DENIED;
}

 

RoleVoter

 

角色投票器。用于 ConfigAttribute#getAttribute() 中配置为角色的授权决策。其默认前缀为 ROLE_,可以自定义,也可以设置为空,直接使用角色标识进行判断。这就意味着,任何属性都可以使用该投票器投票,也就偏离了该投票器的本意,是不可取的。

public int vote(Authentication authentication, Object object,
      Collection attributes) {
    if (authentication == null) {
        return ACCESS_DENIED;
    }
    int result = ACCESS_ABSTAIN;
    Collection authorities = extractAuthorities(authentication);
​
    for (ConfigAttribute attribute : attributes) {
        if (this.supports(attribute)) {
            result = ACCESS_DENIED;
​
            // Attempt to find a matching granted authority
            for (GrantedAuthority authority : authorities) {
                if (attribute.getAttribute().equals(authority.getAuthority())) {
                    return ACCESS_GRANTED;
                }
            }
        }
    }
​
    return result;
}

 

注意,决策策略比较简单,用户只需拥有任一当前请求需要的角色即可,不必全部拥有

 

 

RoleHierarchyVoter

 

基于 RoleVoter,唯一的不同就是该投票器中的角色是附带上下级关系的。也就是说,角色A包含角色B,角色B包含 角色C,此时,如果用户拥有角色A,那么理论上可以同时拥有角色B、角色C的全部资源访问权限。

/**
 * Returns an array of all reachable authorities.
 * 

* Reachable authorities are the directly assigned authorities plus all authorities * that are (transitively) reachable from them in the role hierarchy. *

* Example:
* Role hierarchy: ROLE_A > ROLE_B and ROLE_B > ROLE_C.
* Directly assigned authority: ROLE_A.
* Reachable authorities: ROLE_A, ROLE_B, ROLE_C. * * @param authorities - List of the directly assigned authorities. * @return List of all reachable authorities given the assigned authorities. */ public Collection getReachableGrantedAuthorities( Collection authorities);

 

注意,同 RoleVoter 的决策策略,用户只需拥有任一当前请求需要的角色即可,不必全部拥有

以上就是常用的投票器,接下来,我们自定义一个投票器,与 RoleVoter 策略正好相反,必须拥有当前请求所需的全部角色才能访问。尝试一下。

@Override
public int vote(Authentication authentication, Object object, Collection attributes) {
    if (authentication == null) {
        return ACCESS_DENIED;
    }
​
    int result = ACCESS_ABSTAIN;
    Collection authorities = extractAuthorities(authentication);
​
    for (ConfigAttribute attribute : attributes) {
        if (this.supports(attribute)) {
            result = ACCESS_DENIED;
​
            // Attempt to find all matching granted authority
            for (GrantedAuthority authority : authorities) {
                if (attribute.getAttribute().equals(authority.getAuthority())) {
                    result = ACCESS_GRANTED;
                    break;
                }
            }
​
            if (result == ACCESS_DENIED) {
                return ACCESS_DENIED;
            }
        }
    }
​
    return result;
}

 

其它逻辑包括 rolePrefix、supports等,与 RoleVoter完全一致,只修改决策部分的逻辑:只要当前请求所需任一角色不在用户拥有的角色范围内,即代表没有授权,拒绝访问。

其它详细源码,请参考文末源码链接,可自行下载后阅读。

 

 

源码


github

 

https://github.com/liuminglei/SpringSecurityLearning/tree/master/19

 

gitee

 

https://gitee.com/xbd521/SpringSecurityLearning/tree/master/19

 

 


 

回复以下关键字,获取更多资源

 

SpringCloud进阶之路 | Java 基础 | 微服务 | JAVA WEB | JAVA 进阶 | JAVA 面试 | MK 精讲

 

 

 

 

笔者开通了个人微信公众号【银河架构师】,分享工作、生活过程中的心得体会,填坑指南,技术感悟等内容,会比博客提前更新,欢迎订阅。

技术资料领取方法:关注公众号,回复微服务,领取微服务相关电子书;回复MK精讲,领取MK精讲系列电子书;回复JAVA 进阶,领取JAVA进阶知识相关电子书;回复JAVA面试,领取JAVA面试相关电子书,回复JAVA WEB领取JAVA WEB相关电子书。

 

你可能感兴趣的:(Web安全)