AccessDecisionManager 顾名思义,访问决策管理器。
Makes a final access control (authorization) decision
做出最终的访问控制(授权)决定。
而常用的 AccessDecisionManager 有三个,分别介绍一下其权限决策逻辑。
Spring Security 框架默认的 AccessDecisionManager。
/**
* Simple concrete implementation of
* {@link org.springframework.security.access.AccessDecisionManager} that grants access if
* any AccessDecisionVoter
returns an affirmative response.
*/
从其注释能看出,只要任一 AccessDecisionVoter 返回肯定的结果,便授予访问权限。
再看一下其决策逻辑。
public void decide(Authentication authentication, Object object,
Collection configAttributes) throws AccessDeniedException {
int deny = 0;
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes);
if (logger.isDebugEnabled()) {
logger.debug("Voter: " + voter + ", returned: " + result);
}
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
return;
case AccessDecisionVoter.ACCESS_DENIED:
deny++;
break;
default:
break;
}
}
if (deny > 0) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
其决策逻辑也比较容易看懂,所有当前请求需要的 ConfigAttributes ,全部交给 AccessDecisionVoter 进行投票。只要任一 AccessDecisionVoter 授予访问权限,便返回,不再继续决策判断。否则,便抛出访问授权拒绝的异常,即:AccessDeniedException。
注意,最后是根据配置,判断如果全部 AccessDecisionVoter 都授权决策中立时的授权决策。
少数服从多数授权访问决策方案。
public void decide(Authentication authentication, Object object,
Collection configAttributes) throws AccessDeniedException {
int grant = 0;
int deny = 0;
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes);
if (logger.isDebugEnabled()) {
logger.debug("Voter: " + voter + ", returned: " + result);
}
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
grant++;
break;
case AccessDecisionVoter.ACCESS_DENIED:
deny++;
break;
default:
break;
}
}
if (grant > deny) {
return;
}
if (deny > grant) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
if ((grant == deny) && (grant != 0)) {
if (this.allowIfEqualGrantedDeniedDecisions) {
return;
}
else {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
少数服从多数判断逻辑,并不需要过多的解释。只需要注意一点,那就是授予权限和拒绝权限相等时的逻辑。其实,该决策器也考虑到了这一点,所以提供了 allowIfEqualGrantedDeniedDecisions 参数,用于给用户提供自定义的机会,其默认值为 true,即代表允许授予权限和拒绝权限相等,且同时也代表授予访问权限。
最严格的的授权决策器。要求所有 AccessDecisionVoter 均返回肯定的结果时,才代表授予权限。
/**
* Simple concrete implementation of
* {@link org.springframework.security.access.AccessDecisionManager} that requires all
* voters to abstain or grant access.
*/
其决策逻辑如下。
public void decide(Authentication authentication, Object object,
Collection attributes) throws AccessDeniedException {
int grant = 0;
List singleAttributeList = new ArrayList<>(1);
singleAttributeList.add(null);
for (ConfigAttribute attribute : attributes) {
singleAttributeList.set(0, attribute);
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, singleAttributeList);
if (logger.isDebugEnabled()) {
logger.debug("Voter: " + voter + ", returned: " + result);
}
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
grant++;
break;
case AccessDecisionVoter.ACCESS_DENIED:
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied",
"Access is denied"));
default:
break;
}
}
}
// To get this far, there were no deny votes
if (grant > 0) {
return;
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
可以看到,同前两个决策器不同之处在于,循环将每一个当前请求需要的 ConfigAttribute 传递给 AccessDecisionVoter 进行决策,而不是全部传递过去。这就代表每一个 ConfigAttribute 每一个 AccessDecisionVoter 均需返回肯定的结果才可以授予权限。所以,最为严格。
既然了解了 AccessDecisionManager 的功用,那么我们就可以按照自身业务场景进行定制。比如前面提到的动态资源授权。我们假设用户拥有任一角色即可访问,那么所需的逻辑如下。
@Override
public void decide(Authentication authentication, Object object, Collection attributes) throws AccessDeniedException {
Collection extends GrantedAuthority> authorities = extractAuthorities(authentication);
for (ConfigAttribute attribute : attributes) {
if (this.supports(attribute)) {
// Attempt to find a matching granted authority
for (GrantedAuthority authority : authorities) {
if (attribute.getAttribute().equals(authority.getAuthority())) {
return;
}
}
logger.warn("current user not have the '{}' attribute.", attribute);
}
}
logger.warn("current request should be have at least one of the attributes {}.", attributes);
throw new AccessDeniedException("Access is denied.");
}
我们把当前请求所需的所有 ConfigAttribute 传递给 AccessDecisionVoter 进行决策,只要任一与用户拥有 GrantedAuthority 匹配,即代表授予访问权限。
其它详细源码,请参考文末源码链接,可自行下载后阅读。
https://github.com/liuminglei/SpringSecurityLearning/tree/master/21
https://gitee.com/xbd521/SpringSecurityLearning/tree/master/21
回复以下关键字,获取更多资源
SpringCloud进阶之路 | Java 基础 | 微服务 | JAVA WEB | JAVA 进阶 | JAVA 面试 | MK 精讲
笔者开通了个人微信公众号【银河架构师】,分享工作、生活过程中的心得体会,填坑指南,技术感悟等内容,会比博客提前更新,欢迎订阅。
技术资料领取方法:关注公众号,回复微服务,领取微服务相关电子书;回复MK精讲,领取MK精讲系列电子书;回复JAVA 进阶,领取JAVA进阶知识相关电子书;回复JAVA面试,领取JAVA面试相关电子书,回复JAVA WEB领取JAVA WEB相关电子书。