明天咱们来聊一聊 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 办法。
@OverrideCollection<? 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 中的投票器和决策器,对于受权的更多常识,松哥下篇文章持续和小伙伴们细聊。