为了后续对 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 统计所有的投票器表决后,来做最终的授权决策。
先来看几种常用的投票器。
最常用的,也是 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 的相关介绍。
针对 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;
}
}
用于处理基于注解 @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;
}
角色投票器。用于 ConfigAttribute#getAttribute() 中配置为角色的授权决策。其默认前缀为 ROLE_,可以自定义,也可以设置为空,直接使用角色标识进行判断。这就意味着,任何属性都可以使用该投票器投票,也就偏离了该投票器的本意,是不可取的。
public int vote(Authentication authentication, Object object,
Collection attributes) {
if (authentication == null) {
return ACCESS_DENIED;
}
int result = ACCESS_ABSTAIN;
Collection extends GrantedAuthority> 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;
}
注意,决策策略比较简单,用户只需拥有任一当前请求需要的角色即可,不必全部拥有。
基于 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 extends GrantedAuthority> getReachableGrantedAuthorities(
Collection extends GrantedAuthority> authorities);
注意,同 RoleVoter 的决策策略,用户只需拥有任一当前请求需要的角色即可,不必全部拥有。
以上就是常用的投票器,接下来,我们自定义一个投票器,与 RoleVoter 策略正好相反,必须拥有当前请求所需的全部角色才能访问。尝试一下。
@Override
public int vote(Authentication authentication, Object object, Collection attributes) {
if (authentication == null) {
return ACCESS_DENIED;
}
int result = ACCESS_ABSTAIN;
Collection extends GrantedAuthority> 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完全一致,只修改决策部分的逻辑:只要当前请求所需任一角色不在用户拥有的角色范围内,即代表没有授权,拒绝访问。
其它详细源码,请参考文末源码链接,可自行下载后阅读。
https://github.com/liuminglei/SpringSecurityLearning/tree/master/19
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相关电子书。