明天咱们来聊一聊 Spring Security 中的表决机制与投票器。
当用户想拜访 Spring Security 中一个受爱护的资源时,用户具备一些角色,该资源的拜访也须要一些角色,在比对用户具备的角色和资源须要的角色时,就会用到投票器和表决机制。
当用户想要拜访某一个资源时,投票器依据用户的角色投出赞成或者反对票,表决形式则依据投票器的后果进行表决。
在 Spring Security 中,默认提供了三种表决机制,当然,咱们也能够不必零碎提供的表决机制和投票器,而是齐全本人来定义,这也是能够的。
本文松哥将和大家重点介绍三种表决机制和默认的投票器。
1. 投票器
先来看投票器。
在 Spring Security 中,投票器是由 AccessDecisionVoter 接口来标准的,咱们来看下 AccessDecisionVoter 接口的实现:
能够看到,投票器的实现有好多种,咱们能够抉择其中一种或多种投票器,也能够自定义投票器,默认的投票器是 WebExpressionVoter。
咱们来看 AccessDecisionVoter 的定义:
public interface AccessDecisionVoter<S> {
int ACCESS_GRANTED = 1;
int ACCESS_ABSTAIN = 0;
int ACCESS_DENIED = -1;
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
int vote(Authentication authentication, S object,
Collection<ConfigAttribute> attributes);
}
我略微解释下:
- 首先一上来定义了三个常量,从常量名字中就能够看出每个常量的含意,1 示意赞成;0 示意弃权;-1 示意回绝。
- 两个 supports 办法用来判断投票器是否反对以后申请。
- vote 则是具体的投票办法。在不同的实现类中实现。三个参数,authentication 示意以后登录主体;object 是一个 ilterInvocation,里边封装了以后申请;attributes 示意以后所拜访的接口所须要的角色汇合。
咱们来别离看下几个投票器的实现。
1.1 RoleVoter
RoleVoter 次要用来判断以后申请是否具备该接口所须要的角色,咱们来看下其 vote 办法:
public int vote(Authentication authentication, Object object,
Collection<ConfigAttribute> 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;
for (GrantedAuthority authority : authorities) {if (attribute.getAttribute().equals(authority.getAuthority())) {return ACCESS_GRANTED;}
}
}
}
return result;
}
这个办法的判断逻辑很简略,如果以后登录主体为 null,则间接返回 ACCESS_DENIED 示意回绝拜访;否则就从以后登录主体 authentication 中抽取出角色信息,而后和 attributes 进行比照,如果具备 attributes 中所需角色的任意一种,则返回 ACCESS_GRANTED 示意容许拜访。例如 attributes 中的角色为 [a,b,c],以后用户具备 a,则容许拜访,不须要三种角色同时具备。
另外还有一个须要留神的中央,就是 RoleVoter 的 supports 办法,咱们来看下:
public class RoleVoter implements AccessDecisionVoter<Object> {
private String rolePrefix = "ROLE_";
public String getRolePrefix() {return rolePrefix;}
public void setRolePrefix(String rolePrefix) {this.rolePrefix = rolePrefix;}
public boolean supports(ConfigAttribute attribute) {if ((attribute.getAttribute() != null)
&& attribute.getAttribute().startsWith(getRolePrefix())) {return true;}
else {return false;}
}
public boolean supports(Class<?> clazz) {return true;}
}
能够看到,这里波及到了一个 rolePrefix 前缀,这个前缀是 ROLE_
,在 supports 办法中,只有主体角色前缀是 ROLE_
,这个 supoorts 办法才会返回 true,这个投票器才会失效。
1.2 RoleHierarchyVoter
RoleHierarchyVoter 是 RoleVoter 的一个子类,在 RoleVoter 角色判断的根底上,引入了角色分层治理,也就是角色继承,对于角色继承,小伙伴们能够参考松哥之前的文章(Spring Security 中如何让下级领有上级的所有权限?)。
RoleHierarchyVoter 类的 vote 办法和 RoleVoter 统一,惟一的区别在于 RoleHierarchyVoter 类重写了 extractAuthorities 办法。
@Override
Collection<? extends GrantedAuthority> extractAuthorities(Authentication authentication) {
return roleHierarchy.getReachableGrantedAuthorities(authentication
.getAuthorities());
}
角色分层之后,须要通过 getReachableGrantedAuthorities 办法获取理论具备的角色,具体请参考:[Spring Security 中如何让下级领有上级的所有权限?]() 一文。
1.3 WebExpressionVoter
这是一个基于表达式权限管制的投票器,松哥前面专门花点工夫和小伙伴们聊一聊基于表达式的权限管制,这里咱们先不做过多开展,简略看下它的 vote 办法:
public int vote(Authentication authentication, FilterInvocation fi,
Collection<ConfigAttribute> attributes) {
assert authentication != null;
assert fi != null;
assert attributes != null;
WebExpressionConfigAttribute weca = findConfigAttribute(attributes);
if (weca == null) {return ACCESS_ABSTAIN;}
EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,
fi);
ctx = weca.postProcess(ctx, fi);
return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED
: ACCESS_DENIED;
}
如果你纯熟应用 SpEL 的话,这段代码应该说还是很好了解的,不过依据我的教训,理论工作中用到 SpEL 场景尽管有,然而不多,所以可能有很多小伙伴并不理解 SpEL 的用法,这个须要小伙伴们自行温习下,我也给大家举荐一篇还不错的文章:https://www.cnblogs.com/larryzeal/p/5964621.html。
这里代码实际上就是依据传入的 attributes 属性构建 weca 对象,而后依据传入的 authentication 参数构建 ctx 对象,最初调用 evaluateAsBoolean 办法去判断权限是否匹配。
下面介绍这三个投票器是咱们在理论开发中应用较多的三个。
1.4 其余
另外还有几个比拟冷门的投票器,松哥也略微说下,小伙伴们理解下。
Jsr250Voter
解决 Jsr-250 权限注解的投票器,如 @PermitAll
,@DenyAll
等。
AuthenticatedVoter
AuthenticatedVoter 用于判断 ConfigAttribute 上是否领有 IS_AUTHENTICATED_FULLY、IS_AUTHENTICATED_REMEMBERED、IS_AUTHENTICATED_ANONYMOUSLY 三种角色。
IS_AUTHENTICATED_FULLY 示意以后认证用户必须是通过用户名 / 明码的形式认证的,通过 RememberMe 的形式认证有效。
IS_AUTHENTICATED_REMEMBERED 示意以后登录用户必须是通过 RememberMe 的形式实现认证的。
IS_AUTHENTICATED_ANONYMOUSLY 示意以后登录用户必须是匿名用户。
当我的项目引入 RememberMe 并且想辨别不同的认证形式时,能够思考这个投票器。
AbstractAclVoter
提供编写域对象 ACL 选项的帮忙办法,没有绑定到任何特定的 ACL 零碎。
PreInvocationAuthorizationAdviceVoter
应用 @PreFilter 和 @PreAuthorize 注解解决的权限,通过 PreInvocationAuthorizationAdvice 来受权。
当然,如果这些投票器不能满足需要,也能够自定义。
2. 表决机制
一个申请不肯定只有一个投票器,也可能有多个投票器,所以在投票器的根底上咱们还须要表决机制。
表决相干的类次要是三个:
- AffirmativeBased
- ConsensusBased
- UnanimousBased
他们的继承关系如上图。
三个决策器都会把我的项目中的所有投票器调用一遍,默认应用的决策器是 AffirmativeBased。
三个决策器的区别如下:
- AffirmativeBased:有一个投票器批准了,就通过。
- ConsensusBased:少数投票器批准就通过,平局的话,则看 allowIfEqualGrantedDeniedDecisions 参数的取值。
- UnanimousBased 所有投票器都批准,申请才通过。
这里的具体判断逻辑比较简单,松哥就不贴源码了,感兴趣的小伙伴能够本人看看。
3. 在哪里配置
当咱们应用基于表达式的权限管制时,像上面这样:
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/user/**").hasRole("user")
.anyRequest().fullyAuthenticated()
那么默认的投票器和决策器是在 AbstractInterceptUrlConfigurer#createDefaultAccessDecisionManager 办法中配置的:
private AccessDecisionManager createDefaultAccessDecisionManager(H http) {AffirmativeBased result = new AffirmativeBased(getDecisionVoters(http));
return postProcess(result);
}
List<AccessDecisionVoter<?>> getDecisionVoters(H http) {List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>();
WebExpressionVoter expressionVoter = new WebExpressionVoter();
expressionVoter.setExpressionHandler(getExpressionHandler(http));
decisionVoters.add(expressionVoter);
return decisionVoters;
}
这里就能够看到默认的决策器和投票器,并且决策器 AffirmativeBased 对象创立好之后,还调用 postProcess 办法注册到 Spring 容器中去了,联合松哥本系列后面的文章,大家晓得,如果咱们想要批改该对象就非常容易了:
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/user/**").hasRole("user")
.anyRequest().fullyAuthenticated()
.withObjectPostProcessor(new ObjectPostProcessor<AffirmativeBased>() {
@Override
public <O extends AffirmativeBased> O postProcess(O object) {List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>();
decisionVoters.add(new RoleHierarchyVoter(roleHierarchy()));
AffirmativeBased affirmativeBased = new AffirmativeBased(decisionVoters);
return (O) affirmativeBased;
}
})
.and()
.csrf()
.disable();
这里只是给大家一个演示,失常来说咱们是不须要这样批改的 。当咱们应用不同的权限配置形式时,会有主动配置对应的投票器和决策器。或者咱们手动配置投票器和决策器,如果是系统配置好的,大部分状况下并不需要咱们批改。
4. 小结
本文次要和小伙伴们简略分享一下 Spring Security 中的投票器和决策器,对于受权的更多常识,松哥下篇文章持续和小伙伴们细聊。