第九期 AccessDecisionManager組件介紹
作為訪問(wèn)控制的最后一期,但確實(shí)整個(gè)章節(jié)部分里最簡(jiǎn)單的一部分。ConfigAttribute負(fù)責(zé)表述規(guī)則,AccessDecisionVoter負(fù)責(zé)為規(guī)則表決,但最終的訪問(wèn)授權(quán)是否通過(guò)是由AccessDecisionManager進(jìn)行決策的。
這一期我們將主要介紹Spring Security中提供的三種主要決策模型。
一、AccessDecisionManager接口說(shuō)明
AccessDecisionManager的接口表述非常的簡(jiǎn)單,簡(jiǎn)單來(lái)說(shuō)就一個(gè)主要功——為當(dāng)前的訪問(wèn)規(guī)則進(jìn)行決策,是否給予訪問(wèn)的權(quán)限。無(wú)論是decide方法還是supports方法,AccessDecisionManager本身并不完成相關(guān)的邏輯,全部交由其管理的AccessDecisionVoter依次去判斷與執(zhí)行。而根據(jù)decide的邏輯規(guī)則不同,Spring Security中分別存在三種不同decide決策規(guī)則的AccessDecisionManager,它們分別是:
- AffirmativeBased
- UnanimousBased
- ConsensusBased
在Spring Security默認(rèn)設(shè)置中,使用的是AffirmativeBased。
AccessDecisionManager接口
在詳細(xì)介紹三種AccessDecisionManager的實(shí)現(xiàn)類前,我們先再來(lái)梳理下AccessDecisionManager與AccessDecisionVoter的在決策框架中的關(guān)系。
在框架設(shè)計(jì)中AccessDecisionManager是AccessDecisionVoter的集合類,管理著對(duì)于不同規(guī)則進(jìn)行判斷與表決的AccessDecisionVoter們。
但不同的是,AccessDecisionVoter分別都只會(huì)對(duì)自己支持的規(guī)則進(jìn)行表決,如一個(gè)資源的訪問(wèn)規(guī)則存在多個(gè)并行時(shí),便不能以某一個(gè)AccessDecisionVoter的表決作為最終的訪問(wèn)授權(quán)結(jié)果。AccessDecisionManager的職責(zé)便是在這種場(chǎng)景下,匯總所有AccessDecisionVoter的表決結(jié)果后給出一個(gè)最終的決策。從而導(dǎo)致框架中預(yù)設(shè)了三種不同決策規(guī)則的AccessDecisionManager的實(shí)現(xiàn)類。

二、一票通過(guò)AffirmativeBased
第一個(gè)我們來(lái)介紹,Spring Security中默認(rèn)提供的訪問(wèn)決策模型AffirmativeBased。一句話來(lái)說(shuō)AffirmativeBased的邏輯就是一票通過(guò)——當(dāng)前只要存在任何一個(gè)投了贊同表的AccessDecisionVoter便會(huì)最終給予相關(guān)授權(quán)。
affirmative
adj. 肯定的;積極的
n. 肯定語(yǔ);贊成的一方
假設(shè)存在資源A,在RoleVoter中要求有Admin的角色,而在MinutedOddVoter中缺只要是奇數(shù)分鐘則可以訪問(wèn)。那么在AffirmativeBased模型下,即時(shí)用于沒(méi)有Admin的角色,只要滿足奇數(shù)分鐘的條件一樣可以訪問(wèn)目標(biāo)資源。
@Secured({"IS_AUTHENTICATED_FULLY","ROLE_USER","MINUTE_ODD"})
@RequestMapping("/")
public String root(@Autowired Authentication authentication) {
return "index";
}
當(dāng)我們奇數(shù)分鐘數(shù)訪問(wèn)對(duì)應(yīng)資源的時(shí)候:
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
即使RoleVoter與AuthenticatedVoter存在明確的反對(duì),但是因?yàn)?code>MinuteBasedVoter滿足了時(shí)間的要求,一樣會(huì)得到一個(gè)肯定的結(jié)果。
這邊有一個(gè)經(jīng)驗(yàn),在默認(rèn)的AffirmativeBased的模型下客制化AccessDecisionVoter如果不是很決定性的規(guī)則,諸如一些輔助性的訪問(wèn)限制避免投出明確的贊同表,而是換個(gè)角度,投出明確的反對(duì)票,如不滿足反對(duì)的情況可以投出棄權(quán)票。我們?cè)谏弦黄诳椭苹?code>MinuteBasedVoter便是一個(gè)不好的反面教材_。
三、一票否決UnanimousBased
第二個(gè),我們?cè)賮?lái)介紹一個(gè)備選的決策規(guī)則,即UnanimousBased所代表的一票否則制,所有人都沒(méi)有反對(duì)意見(jiàn)。
unanimous
adj. 全體一致的;意見(jiàn)一致的;無(wú)異議的
其規(guī)則也十分容易懂,只要任意一個(gè)AccessDecisionVoter投出了反對(duì)票,則無(wú)論有多少個(gè)贊同票都無(wú)法授權(quán)訪問(wèn)權(quán)限。UnanimousBased代表了與AffirmativeBased完全對(duì)立的規(guī)則,有點(diǎn)類似五常表決的一票否則制,比如前幾年著名的新聞“土耳其要求取消俄羅斯的一票否決票,這個(gè)提案被俄羅斯一票否決了”。
同樣回到代碼上來(lái),我們通過(guò)調(diào)整Java Config配置代碼將使用的AccessDecisionManager實(shí)現(xiàn)變更為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);
}
}
對(duì)于同樣的場(chǎng)景下,如用戶已經(jīng)登錄并擁有了對(duì)應(yīng)的權(quán)限而因?yàn)楫?dāng)前時(shí)間不是偶數(shù)分鐘,那么最終的決策結(jié)果因?yàn)橛幸黄狈駴Q變?yōu)榱?strong>不可訪問(wèn)
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);
四、少數(shù)服從多數(shù)ConsensusBased
最后出場(chǎng)的ConsensusBased可能是三個(gè)規(guī)則里最“民主”,即少數(shù)服從多數(shù)制。
consensus
n. 一致;輿論;合意
ConsensusBased對(duì)所有投票的AccessDecisionVoter的意見(jiàn)進(jìn)行匯總,以數(shù)量多那一方的結(jié)果為準(zhǔn)。
但是存在一種特殊情況——平票:如果產(chǎn)生平票則根據(jù)配置allowIfEqualGrantedDeniedDecisions來(lái)判斷是否通過(guò),在默認(rèn)情況下allowIfEqualGrantedDeniedDecisions值是true。
同樣的我們修改Java Config來(lái)測(cè)試下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);//可以調(diào)整平票邏輯
return consensusBased;
}
}
我們同樣在偶數(shù)分鐘訪問(wèn),在登錄后訪問(wèn)受限制的資源:
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的表現(xiàn)不同,因?yàn)橘澩贝笥诜駥?duì)票所以我們最終還是獲取了訪問(wèn)的權(quán)限。
結(jié)尾
作為訪問(wèn)控制的最后一個(gè)組件,由于有了之前的鋪墊和了解,三種決策規(guī)則相比之下會(huì)顯得簡(jiǎn)單很多。并且在通常的情況下,對(duì)于AccessDecisionManager我們也不太會(huì)存在任何客制化的可能性。我們只需要了解如何選擇合適的AccessDecisionManager與如何編寫相關(guān)的Java配置代碼即可。
從下一期開(kāi)始,我們將進(jìn)入新的主題開(kāi)始介紹Spring Security中的config包下關(guān)于配置的一些內(nèi)容。
我們下期再見(jiàn)。
