Spring Security 權限管理的投票器與表決機制

今天我們來聊一聊 Spring Security 中的表決機制與投票器。html

當用戶想訪問 Spring Security 中一個受保護的資源時,用戶具有一些角色,該資源的訪問也須要一些角色,在比對用戶具有的角色和資源須要的角色時,就會用到投票器和表決機制。java

當用戶想要訪問某一個資源時,投票器根據用戶的角色投出同意或者反對票,表決方式則根據投票器的結果進行表決。express

在 Spring Security 中,默認提供了三種表決機制,固然,咱們也能夠不用系統提供的表決機制和投票器,而是徹底本身來定義,這也是能夠的。ide

本文鬆哥將和你們重點介紹三種表決機制和默認的投票器。post

1.投票器

先來看投票器。this

在 Spring Security 中,投票器是由 AccessDecisionVoter 接口來規範的,咱們來看下 AccessDecisionVoter 接口的實現:lua

能夠看到,投票器的實現有好多種,咱們能夠選擇其中一種或多種投票器,也能夠自定義投票器,默認的投票器是 WebExpressionVoter。spa

咱們來看 AccessDecisionVoter 的定義:code

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);
}

我稍微解釋下:csrf

  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 中的投票器和決策器,關於受權的更多知識,鬆哥下篇文章繼續和小夥伴們細聊。

相關文章
相關標籤/搜索