关于spring:Spring-Security-权限管理的投票器与表决机制

2次阅读

共计 6101 个字符,预计需要花费 16 分钟才能阅读完成。

明天咱们来聊一聊 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. 首先一上来定义了三个常量,从常量名字中就能够看出每个常量的含意,1 示意赞成;0 示意弃权;-1 示意回绝。
  2. 两个 supports 办法用来判断投票器是否反对以后申请。
  3. 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 中的投票器和决策器,对于受权的更多常识,松哥下篇文章持续和小伙伴们细聊。

正文完
 0