Spring Security教程 Vol 9. AccessDecisionManager元件介紹

廢柴大叔阿基拉發表於2019-04-23

第九期 AccessDecisionManager元件介紹

作為訪問控制的最後一期,但確實整個章節部分裡最簡單的一部分。ConfigAttribute負責表述規則,AccessDecisionVoter負責為規則表決,但最終的訪問授權是否通過是由AccessDecisionManager進行決策的。 這一期我們將主要介紹Spring Security中提供的三種主要決策模型。

一、AccessDecisionManager介面說明

AccessDecisionManager的介面表述非常的簡單,簡單來說就一個主要功——為當前的訪問規則進行決策,是否給予訪問的許可權。無論是decide方法還是supports方法,AccessDecisionManager本身並不完成相關的邏輯,全部交由其管理的AccessDecisionVoter依次去判斷與執行。而根據decide的邏輯規則不同,Spring Security中分別存在三種不同decide決策規則的AccessDecisionManager,它們分別是:

  • AffirmativeBased
  • UnanimousBased
  • ConsensusBased 在Spring Security預設設定中,使用的是AffirmativeBased
    AccessDecisionManager介面

在詳細介紹三種AccessDecisionManager的實現類前,我們先再來梳理下AccessDecisionManagerAccessDecisionVoter的在決策框架中的關係。 在框架設計中AccessDecisionManagerAccessDecisionVoter的集合類,管理著對於不同規則進行判斷與表決的AccessDecisionVoter們。 但不同的是,AccessDecisionVoter分別都只會對自己支援的規則進行表決,如一個資源的訪問規則存在多個並行時,便不能以某一個AccessDecisionVoter的表決作為最終的訪問授權結果。AccessDecisionManager的職責便是在這種場景下,彙總所有AccessDecisionVoter的表決結果後給出一個最終的決策。從而導致框架中預設了三種不同決策規則的AccessDecisionManager的實現類。

image.png

二、一票通過AffirmativeBased

第一個我們來介紹,Spring Security中預設提供的訪問決策模型AffirmativeBased。一句話來說AffirmativeBased的邏輯就是一票通過——當前只要存在任何一個投了贊同表的AccessDecisionVoter便會最終給予相關授權。

affirmative adj. 肯定的;積極的 n. 肯定語;贊成的一方

假設存在資源A,在RoleVoter中要求有Admin的角色,而在MinutedOddVoter中缺只要是奇數分鐘則可以訪問。那麼在AffirmativeBased模型下,即時用於沒有Admin的角色,只要滿足奇數分鐘的條件一樣可以訪問目標資源。

    @Secured({"IS_AUTHENTICATED_FULLY","ROLE_USER","MINUTE_ODD"})
    @RequestMapping("/")
    public String root(@Autowired Authentication authentication) {
        return "index";
    }
複製程式碼

當我們奇數分鐘數訪問對應資源的時候:

Voter: org.springframework.security.access.vote.RoleVoter@508280a4, returned: -1
Voter: org.springframework.security.access.vote.AuthenticatedVoter@2846f995, returned: -1
Voter: com.newnil.demo.security.MinuteBasedVoter@1ec9ec13, returned: 1
Authorization successful
複製程式碼

即使RoleVoterAuthenticatedVoter存在明確的反對,但是因為MinuteBasedVoter滿足了時間的要求,一樣會得到一個肯定的結果。 這邊有一個經驗,在預設的AffirmativeBased的模型下客製化AccessDecisionVoter如果不是很決定性的規則,諸如一些輔助性的訪問限制避免投出明確的贊同表,而是換個角度,投出明確的反對票,如不滿足反對的情況可以投出棄權票。我們在上一期客製化的MinuteBasedVoter便是一個不好的反面教材^_^。

三、一票否決UnanimousBased

第二個,我們再來介紹一個備選的決策規則,即UnanimousBased所代表的一票否則制,所有人都沒有反對意見。

unanimous adj. 全體一致的;意見一致的;無異議的

其規則也十分容易懂,只要任意一個AccessDecisionVoter投出了反對票,則無論有多少個贊同票都無法授權訪問許可權。UnanimousBased代表了與AffirmativeBased完全對立的規則,有點類似五常表決的一票否則制,比如前幾年著名的新聞“土耳其要求取消俄羅斯的一票否決票,這個提案被俄羅斯一票否決了”。 同樣回到程式碼上來,我們通過調整Java Config配置程式碼將使用的AccessDecisionManager實現變更為UnanimousBased

@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
@Configuration
public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
    @Override
    protected AccessDecisionManager accessDecisionManager() {
        List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList();
        ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice();
        expressionAdvice.setExpressionHandler(this.getExpressionHandler());
        decisionVoters.add(new RoleVoter());
        decisionVoters.add(new AuthenticatedVoter());
        decisionVoters.add(new MinuteBasedVoter());
        return new UnanimousBased(decisionVoters);
    }
}
複製程式碼

對於同樣的場景下,如使用者已經登入並擁有了對應的許可權而因為當前時間不是偶數分鐘,那麼最終的決策結果因為有一票否決變為了不可訪問

 Voter: org.springframework.security.access.vote.RoleVoter@ddc490, returned: 0
 Voter: org.springframework.security.access.vote.AuthenticatedVoter@27f66035, returned: 1
 Voter: com.newnil.demo.security.MinuteBasedVoter@4f6b68aa, returned: -1
Access is denied (user is not anonymous);
複製程式碼

四、少數服從多數ConsensusBased

最後出場的ConsensusBased可能是三個規則裡最“民主”,即少數服從多數制。

consensus n. 一致;輿論;合意 ConsensusBased對所有投票的AccessDecisionVoter的意見進行彙總,以數量多那一方的結果為準。 但是存在一種特殊情況——平票:如果產生平票則根據配置allowIfEqualGrantedDeniedDecisions來判斷是否通過,在預設情況下allowIfEqualGrantedDeniedDecisions值是true。

同樣的我們修改Java Config來測試下ConsensusBased的行為:

@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
@Configuration
public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
    @Override
    protected AccessDecisionManager accessDecisionManager() {
        List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList();
        ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice();
        expressionAdvice.setExpressionHandler(this.getExpressionHandler());
        decisionVoters.add(new RoleVoter());
        decisionVoters.add(new AuthenticatedVoter());
        decisionVoters.add(new MinuteBasedVoter());
        ConsensusBased consensusBased = new ConsensusBased(decisionVoters);
        consensusBased.setAllowIfEqualGrantedDeniedDecisions(false);//可以調整平票邏輯
        return consensusBased;
    }
}
複製程式碼

我們同樣在偶數分鐘訪問,在登入後訪問受限制的資源:

Voter: org.springframework.security.access.vote.RoleVoter@2c1ae72c, returned: 1
Voter: org.springframework.security.access.vote.AuthenticatedVoter@60d5234, returned: 1
Voter: com.newnil.demo.security.MinuteBasedVoter@6a34393c, returned: -1
Authorization successful
複製程式碼

與之前UnanimousBased的表現不同,因為贊同票大於否對票所以我們最終還是獲取了訪問的許可權。

結尾

作為訪問控制的最後一個元件,由於有了之前的鋪墊和了解,三種決策規則相比之下會顯得簡單很多。並且在通常的情況下,對於AccessDecisionManager我們也不太會存在任何客製化的可能性。我們只需要瞭解如何選擇合適的AccessDecisionManager與如何編寫相關的Java配置程式碼即可。 從下一期開始,我們將進入新的主題開始介紹Spring Security中的config包下關於配置的一些內容。 我們下期再見。

相關文章