第八期 AccessDecisionVoter組件介紹
這一期主要我們將介紹訪問控制三劍客負責對授權規(guī)則做角色的組件——AccessDecisionVoter
接口揖盘。以及對Spring Security默認提供的幾個基礎AccessDecisionVoter
實現(xiàn)類做一個詳細的說明乘瓤,最后我們將會客制化一個基于時間的AccessDecisionVoter
實現(xiàn)用于實戰(zhàn)說明憔狞。
-
AccessDecisionVoter
接口說明 - Spring Security的
AccessDecisionVoter
們 - 客制化實例:基于時間的
AccessDecisionVoter
一呐萨、AccessDecisionVoter接口說明
AccessDecisionVoter
主要的職責就是對它所對應的訪問規(guī)則作出判斷帜羊,當前的訪問規(guī)則是否可以得到授權市袖。AccessDecisionVoter
接口的主要方法其實與之前的AuthenticationProvider
非常的相似谴分。
boolean supports(ConfigAttribute attribute);
int vote(Authentication authentication, S object,
Collection<ConfigAttribute> attributes);
- supports方法用于判斷對于當前
ConfigAttribute
訪問規(guī)則是否支持锈麸; - 如果支持的情況下,vote方法對其進行判斷投票返回對應的授權結果牺蹄。
最終的授權結果一共有三種,分別是同意忘伞、棄權和反對。說實話這個規(guī)則和聯(lián)合國安理會投票差不多性質。當前一個訪問可能存在多個規(guī)則的情況下氓奈,每一個AccessDecisionVoter
投出自己的那一票匿刮,最終的投票結果是還是要看當前的投票規(guī)則,比如是超過1/3還是要過半數探颈。而投票規(guī)則的判斷則是被放置了在了AccessDecisionManager
進行完成熟丸。
int ACCESS_GRANTED = 1;
int ACCESS_ABSTAIN = 0;
int ACCESS_DENIED = -1;
二、 Spring Security的AccessDecisionVoter
們
通過上面對于AccessDecisionVoter
的基本介紹伪节,我們得知了一個設計上的大原則:AccessDecisionVoter
的實現(xiàn)是為了滿足對應規(guī)則ConfigAttribute
光羞。大體上來說AccessDecisionVoter
是與ConfigAttribute
一一對應的。
讓我們回一下在上一期我們介紹的主要的幾種ConfigAttribute
實現(xiàn):
- 基于Web表達式的WebExpressionConfigAttribute
- 基于@Secured注解的SecurityConfig
- 基于@Pre-@Post注解的PostInvocationExpressionAttribute
我們可以在下圖中輕松的找到他門對應的AccessDecisionVoter
怀大。
這邊我們重點說一下在客制化場景下被利用的SecurityConfig配置和他默認的兩個AccessDecisionVoter
: - RoleVoter
- AuthenticatedVoter
首先纱兑,我們來回憶下SecurityConfig的使用形式,即利用@Secured注解編寫一個表達式:
@Secured("ROLE_USER")
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
我們了解到了AccessDecisionVoter
和ConfigAttribute
的關聯(lián)關系是通過supports方法進行判斷化借,我們分別對RoleVoter
和AuthenticatedVoter
的supports方法進行瀏覽:
RoleVoter
RoleVoter
是Spring Security中默認基于角色規(guī)則的核心組件潜慎。在UserDetailsService中創(chuàng)建用戶我們都會需要設置對用用戶的角色信息。在默認配置下用戶的角色信息都是以"ROLE_"+角色名的形式存儲的蓖康。
對應的在RoleVoter
的supports方法中會對表達式是否以'ROLE_'開始作為對應啟用規(guī)則的判斷铐炫。如果規(guī)則表達式是以ROLE_開始的,RoleVoter
則會去遍歷對用Authentication是否存在對應的角色蒜焊,如果存在則返回通過倒信,如果不存在則返回拒絕。
public class RoleVoter implements AccessDecisionVoter<Object> {
// ~ Instance fields
// ================================================================================================
private String rolePrefix = "ROLE_";
// ~ Methods
// ========================================================================================================
public String getRolePrefix() {
return rolePrefix;
}
/**
* Allows the default role prefix of <code>ROLE_</code> to be overridden. May be set
* to an empty value, although this is usually not desirable.
*
* @param rolePrefix the new prefix
*/
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;
}
}
}
AuthenticatedVoter
AuthenticatedVoter
的使用場景就比較特殊泳梆,他并不是一個基于身份信息的訪問控制鳖悠,而是對于對應Auhentication
的認證形式的一個判斷。在之前的身份驗證部分我們有了解過优妙,在Spring Security設計中乘综,我們可以銅鼓RememberMeService的方式不使用用戶名和密碼,而是通過存儲于Cookie的信息進行授權登錄套硼。在日常工程中卡辰,對于一些敏感操作,我們要求當前的用戶并不是一個基于歷史進行授權認證的用戶熟菲,比如在進行支付的情況下看政,如果我們希望用戶是在本次訪問中是通過用戶名和密碼進行登錄展開的會話操作,而不是一個基于一個月前cookies進行登錄都有用戶抄罕。在這個場景下我們需要便可以使用@Secured("IS_AUTHENTICATED_FULLY")
去限定用戶是一個通過完全驗證的用戶,而不是通過RememberMe方式認證的用戶于颖。
在AuthenticatedVoter
的supports方法中呆贿,便會判斷當前的表達式是為他所支持的三種認證方法的訪問控制:
- IS_AUTHENTICATED_FULLY
- IS_AUTHENTICATED_REMEMBERED
- IS_AUTHENTICATED_ANONYMOUSLY
如果完全匹配,則會當前的Authentication
對象的授權模式進行判斷,返回相應的投票結果做入。
public class AuthenticatedVoter implements AccessDecisionVoter<Object> {
// ~ Static fields/initializers
// =====================================================================================
public static final String IS_AUTHENTICATED_FULLY = "IS_AUTHENTICATED_FULLY";
public static final String IS_AUTHENTICATED_REMEMBERED = "IS_AUTHENTICATED_REMEMBERED";
public static final String IS_AUTHENTICATED_ANONYMOUSLY = "IS_AUTHENTICATED_ANONYMOUSLY";
// ~ Instance fields
// ================================================================================================
private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
// ~ Methods
// ========================================================================================================
private boolean isFullyAuthenticated(Authentication authentication) {
return (!authenticationTrustResolver.isAnonymous(authentication) && !authenticationTrustResolver
.isRememberMe(authentication));
}
public boolean supports(ConfigAttribute attribute) {
if ((attribute.getAttribute() != null)
&& (IS_AUTHENTICATED_FULLY.equals(attribute.getAttribute())
|| IS_AUTHENTICATED_REMEMBERED.equals(attribute.getAttribute()) || IS_AUTHENTICATED_ANONYMOUSLY
.equals(attribute.getAttribute()))) {
return true;
}
else {
return false;
}
}
}
三冒晰、 客制化實例:基于時間的AccessDecisionVoter
對于AccessDecisionVoter
結構、責任和Spring Security中提供的實現(xiàn)類有了一個基礎的了解后竟块。我們通過一個客制化的實例來加強這部分的理解壶运。
我們將客制化一個基于時間的訪問控制,在系統(tǒng)時間的分鐘數是奇數的情況下才可以被訪問浪秘,比如10點01分可以訪問蒋情,但是10點02分則不可以被訪問。
設計規(guī)則
首先耸携,我們對訪問規(guī)則進行設計棵癣。我們如同RoleVoter
與AuthenticatedVoter
一樣基于@Secured注解的表達式進行擴展。我們擬定的規(guī)則名為"MINUTE_ODD"夺衍,當方法級被注解了@Secured("MINUTE_ODD")情況下狈谊,表示當前方法只有在滿足系統(tǒng)時間的分鐘數為奇數下才可以被訪問。
客制化MinuteBasedVoter
接下來沟沙,我們編寫一個MinuteBasedVoter
擴展AuthenticatedVoter
河劝。
public class MinuteBasedVoter implements AccessDecisionVoter {
}
然后,我們實現(xiàn)對應的suppors方法用于完成我們對我們擬定的規(guī)則的判斷矛紫。當入參ConfigAttribute 的表達式屬性與我們預設的"MINUTE_ODD"一致時丧裁,那么我們便返回true告知框架,MinuteBasedVoter
需要對該規(guī)則進行vote的投票操作含衔。
public class MinuteBasedVoter implements AccessDecisionVoter {
public static final String IS_MINUTE_ODD= "MINUTE_ODD";
@Override
public boolean supports(ConfigAttribute attribute) {
if ((attribute.getAttribute() != null)
&& attribute.getAttribute().equals(IS_MINUTE_ODD)) {
return true;
}
else {
return false;
}
}
@Override
public boolean supports(Class clazz) {
return true;
}
}
最后煎娇,我們將vote的投票核心業(yè)務邏輯完成:當時間為奇數的時候則投贊同票,而在時間為偶數的時候則投一張明確的反對票贪染。
@Override
public int vote(Authentication authentication, Object object, Collection collection) {
if(LocalDateTime.now().getMinute() % 2 != 0){
return ACCESS_GRANTED;
}else{
return ACCESS_DENIED;
}
}
Java Config配置
最后缓呛,說一下如何將新的AccessDecisionVoter
添加到現(xiàn)有的AccessDecisionManager
中。我自己也百度了一下了中文世界和英文世界關于這方便的示例已經官方文檔杭隙,真的是五花八門都有哟绊。最常見的是重新組織了一個AccessDecisionManager注入回Spring Security中,我很不推薦自己在方法中去new一個AccessDecisionManager痰憎。因為AccessDecisionManager的初始化過程中涉及的不只是AccessDecisionVoter
票髓,一不小心可能因為少設置什么組件就導致一部分默認行為沒被正確的配置上去。
我推薦初學者方法是對于擴展Secured這類基于方法級的注解铣耘,單獨新建一個Java Config類洽沟,然后重寫原有框架中初始化AccessDecisionManager
的方法:
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
@Configuration
public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
@Override
protected AccessDecisionManager accessDecisionManager() {
AffirmativeBased affirmativeBased = (AffirmativeBased) super.accessDecisionManager();
affirmativeBased.getDecisionVoters().add(new MinuteBasedVoter());
return affirmativeBased;
}
}
雖然代碼可能丑、有對類型強轉蜗细,相對來說好理解控制很多裆操。
在添加了MethodSecurityConfiguration的Java Config之后怒详,我們在對受到@Secured("MINUTE_ODD")注解限制的controller方式時便會看到以下的投票日志:
Secure object: ReflectiveMethodInvocation: public java.lang.String Attributes: [MINUTE_ODD]
Voter: org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter@456f4439, returned: 0
Voter: org.springframework.security.access.vote.RoleVoter@38b13fa8, returned: 0
Voter: org.springframework.security.access.vote.AuthenticatedVoter@590fa701, returned: 0
Voter: com.newnil.demo.security.MinuteBasedVoter@135c04e9, returned: 1
Authorization successful
AccessDecisionVoter
組件們依次投票,而因為當前時間是奇數踪区,所以我們的MinuteBasedVoter投出一票值為1的贊同票昆烁。
結尾
這一期詳細介紹了AccessDecisionVoter
這一為訪問控制提供核心判斷及投票的組件。同時也通過框架默認提供與客制化實現(xiàn)了解了其工作原理缎岗。
下一期我們將最后一個核心組件AccessDecisionManager
是如何對所有AccessDecisionVoter
的投票結果進行匯總静尼,以及如何以什么評價規(guī)則告知框架最終的授權結果進行說明。
我們下期再見传泊。