目錄
1.1 權限
1.2 調用前的處理
1.2.1 AccessDecisionManager
1.2.2 基於投票的AccessDecisionManager實現
1.3 調用后的處理
1.4 角色的繼承
1.1 權限
所有的Authentication實現類都保存了一個GrantedAuthority列表,其表示用戶所具有的權限。GrantedAuthority是通過AuthenticationManager設置到Authentication對象中的,然后AccessDecisionManager將從Authentication中獲取用戶所具有的GrantedAuthority來鑒定用戶是否具有訪問對應資源的權限。
GrantedAuthority是一個接口,其中只定義了一個getAuthority()方法,其返回值為String類型。該方法允許AccessDecisionManager獲取一個能夠精確代表該權限的字符串。通過返回一個字符串,一個GrantedAuthority能夠很輕易的被大部分AccessDecisionManager讀取。如果一個GrantedAuthority不能夠精確的使用一個String來表示,那么其對應的getAuthority()方法調用應當返回一個null,這表示AccessDecisionManager必須對該GrantedAuthority的實現有特定的支持,從而可以獲取該GrantedAuthority所代表的權限信息。
Spring Security內置了一個GrantedAuthority的實現,SimpleGrantedAuthority。它直接接收一個表示權限信息的字符串,然后getAuthority()方法直接返回該字符串。Spring Security內置的所有AuthenticationProvider都是使用它來封裝Authentication對象的。
1.2 調用前的處理
Spring Security是通過攔截器來控制受保護對象的訪問的,如方法調用和Web請求。在正式訪問受保護對象之前,Spring Security將使用AccessDecisionManager來鑒定當前用戶是否有訪問對應受保護對象的權限。
1.2.1AccessDecisionManager
AccessDecisionManager是由AbstractSecurityInterceptor調用的,它負責鑒定用戶是否有訪問對應資源(方法或URL)的權限。AccessDecisionManager是一個接口,其中只定義了三個方法,其定義如下。
public interface AccessDecisionManager {
/**
* 通過傳遞的參數來決定用戶是否有訪問對應受保護對象的權限
*
* @param authentication 當前正在請求受包含對象的Authentication
* @param object 受保護對象,其可以是一個MethodInvocation、JoinPoint或FilterInvocation。
* @param configAttributes 與正在請求的受保護對象相關聯的配置屬性
*
*/
void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException;
/**
* 表示當前AccessDecisionManager是否支持對應的ConfigAttribute
*/
boolean supports(ConfigAttribute attribute);
/**
* 表示當前AccessDecisionManager是否支持對應的受保護對象類型
*/
boolean supports(Class<?> clazz);
}
decide()方法用於決定authentication是否符合受保護對象要求的configAttributes。supports(ConfigAttribute attribute)方法是用來判斷AccessDecisionManager是否能夠處理對應的ConfigAttribute的。supports(Class<?> clazz)方法用於判斷配置的AccessDecisionManager是否支持對應的受保護對象類型。
1.2.2基於投票的AccessDecisionManager實現
Spring Security已經內置了幾個基於投票的AccessDecisionManager,當然如果需要你也可以實現自己的AccessDecisionManager。以下是Spring Security官方文檔提供的一個圖,其展示了與基於投票的AccessDecisionManager實現相關的類。
使用這種方式,一系列的AccessDecisionVoter將會被AccessDecisionManager用來對Authentication是否有權訪問受保護對象進行投票,然后再根據投票結果來決定是否要拋出AccessDeniedException。AccessDecisionVoter是一個接口,其中定義有三個方法,具體結構如下所示。
public interface AccessDecisionVoter<S> {
intACCESS_GRANTED = 1;
intACCESS_ABSTAIN = 0;
intACCESS_DENIED = -1;
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);
}
vote()方法的返回結果會是AccessDecisionVoter中定義的三個常量之一。ACCESS_GRANTED表示同意,ACCESS_DENIED表示返回,ACCESS_ABSTAIN表示棄權。如果一個AccessDecisionVoter不能判定當前Authentication是否擁有訪問對應受保護對象的權限,則其vote()方法的返回值應當為棄權ACCESS_ABSTAIN。
Spring Security內置了三個基於投票的AccessDecisionManager實現類,它們分別是AffirmativeBased、ConsensusBased和UnanimousBased。
AffirmativeBased的邏輯是這樣的:
(1)只要有AccessDecisionVoter的投票為ACCESS_GRANTED則同意用戶進行訪問;
(2)如果全部棄權也表示通過;
(3)如果沒有一個人投贊成票,但是有人投反對票,則將拋出AccessDeniedException。
ConsensusBased的邏輯是這樣的:
(1)如果贊成票多於反對票則表示通過。
(2)反過來,如果反對票多於贊成票則將拋出AccessDeniedException。
(3)如果贊成票與反對票相同且不等於0,並且屬性allowIfEqualGrantedDeniedDecisions的值為true,則表示通過,否則將拋出異常AccessDeniedException。參數allowIfEqualGrantedDeniedDecisions的值默認為true。
(4)如果所有的AccessDecisionVoter都棄權了,則將視參數allowIfAllAbstainDecisions的值而定,如果該值為true則表示通過,否則將拋出異常AccessDeniedException。參數allowIfAllAbstainDecisions的值默認為false。
UnanimousBased的邏輯與另外兩種實現有點不一樣,另外兩種會一次性把受保護對象的配置屬性全部傳遞給AccessDecisionVoter進行投票,而UnanimousBased會一次只傳遞一個ConfigAttribute給AccessDecisionVoter進行投票。這也就意味着如果我們的AccessDecisionVoter的邏輯是只要傳遞進來的ConfigAttribute中有一個能夠匹配則投贊成票,但是放到UnanimousBased中其投票結果就不一定是贊成了。UnanimousBased的邏輯具體來說是這樣的:
(1)如果受保護對象配置的某一個ConfigAttribute被任意的AccessDecisionVoter反對了,則將拋出AccessDeniedException。
(2)如果沒有反對票,但是有贊成票,則表示通過。
(3)如果全部棄權了,則將視參數allowIfAllAbstainDecisions的值而定,true則通過,false則拋出AccessDeniedException。
1.2.2.1RoleVoter
RoleVoter是Spring Security內置的一個AccessDecisionVoter,其會將ConfigAttribute簡單的看作是一個角色名稱,在投票的時如果擁有該角色即投贊成票。如果ConfigAttribute是以“ROLE_”開頭的,則將使用RoleVoter進行投票。當用戶擁有的權限中有一個或多個能匹配受保護對象配置的以“ROLE_”開頭的ConfigAttribute時其將投贊成票;如果用戶擁有的權限中沒有一個能匹配受保護對象配置的以“ROLE_”開頭的ConfigAttribute,則RoleVoter將投反對票;如果受保護對象配置的ConfigAttribute中沒有以“ROLE_”開頭的,則RoleVoter將棄權。
1.2.2.2AuthenticatedVoter
AuthenticatedVoter也是Spring Security內置的一個AccessDecisionVoter實現。其主要用來區分匿名用戶、通過Remember-Me認證的用戶和完全認證的用戶。完全認證的用戶是指由系統提供的登錄入口進行成功登錄認證的用戶。
AuthenticatedVoter可以處理的ConfigAttribute有IS_AUTHENTICATED_FULLY、IS_AUTHENTICATED_REMEMBERED和IS_AUTHENTICATED_ANONYMOUSLY。如果ConfigAttribute不在這三者范圍之內,則AuthenticatedVoter將棄權。否則將視ConfigAttribute而定,如果ConfigAttribute為IS_AUTHENTICATED_ANONYMOUSLY,則不管用戶是匿名的還是已經認證的都將投贊成票;如果是IS_AUTHENTICATED_REMEMBERED則僅當用戶是由Remember-Me自動登錄,或者是通過登錄入口進行登錄認證時才會投贊成票,否則將投反對票;而當ConfigAttribute為IS_AUTHENTICATED_FULLY時僅當用戶是通過登錄入口進行登錄的才會投贊成票,否則將投反對票。
AuthenticatedVoter是通過AuthenticationTrustResolver的isAnonymous()方法和isRememberMe()方法來判斷SecurityContextHolder持有的Authentication是否為AnonymousAuthenticationToken或RememberMeAuthenticationToken的,即是否為IS_AUTHENTICATED_ANONYMOUSLY和IS_AUTHENTICATED_REMEMBERED。
1.2.2.3自定義Voter
當然,用戶也可以通過實現AccessDecisionVoter來實現自己的投票邏輯。
1.3 調用后的處理
AccessDecisionManager是用來在訪問受保護的對象之前判斷用戶是否擁有訪問該對象的權限。有的時候我們可能會希望在請求執行完成后對返回值做一些修改,當然,你可以簡單的通過AOP來實現這一功能。Spring Security為我們提供了一個AfterInvocationManager接口,它允許我們在受保護對象訪問完成后對返回值進行修改或者進行權限鑒定,看是否需要拋出AccessDeniedException,其將由AbstractSecurityInterceptor的子類進行調用。AfterInvocationManager接口的定義如下。
public interface AfterInvocationManager {
Object decide(Authentication authentication, Object object, Collection<ConfigAttribute> attributes,
Object returnedObject) throws AccessDeniedException;
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
}
以下是Spring Security官方文檔提供的一個AfterInvocationManager構造實現的圖。
類似於AuthenticationManager,AfterInvocationManager擁有一個默認的實現類AfterInvocationProviderManager,其中擁有一個由AfterInvocationProvider組成的集合,AfterInvocationProvider與AfterInvocationManager具有相同的方法定義,在調用AfterInvocationProviderManager中的方法時實際上就是依次調用其中包含的AfterInvocationProvider對應的方法。
需要注意的是AfterInvocationManager需要在受保護對象成功被訪問后才能執行。
1.4 角色的繼承
對於角色繼承這種需求也是經常有的,比如要求ROLE_ADMIN將擁有所有的ROLE_USER所具有的權限。當然我們可以給擁有ROLE_ADMIN角色的用戶同時授予ROLE_USER角色來達到這一效果或者修改需要ROLE_USER進行訪問的資源使用ROLE_ADMIN也可以訪問。Spring Security為我們提供了一種更為簡便的辦法,那就是角色的繼承,它允許我們的ROLE_ADMIN直接繼承ROLE_USER,這樣所有ROLE_USER可以訪問的資源ROLE_ADMIN也可以訪問。定義角色的繼承我們需要在ApplicationContext中定義一個RoleHierarchy,然后再把它賦予給一個RoleHierarchyVoter,之后再把該RoleHierarchyVoter加入到我們基於Voter的AccessDecisionManager中,並指定當前使用的AccessDecisionManager為我們自己定義的那個。以下是一個定義角色繼承的完整示例。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<!-- 通過access-decision-manager-ref指定將要使用的AccessDecisionManager -->
<security:http access-decision-manager-ref="accessDecisionManager">
<security:form-login/>
<security:intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
<security:intercept-url pattern="/**" access="ROLE_USER" />
</security:http>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider
user-service-ref="userDetailsService"/>
</security:authentication-manager>
<bean id="userDetailsService"
class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 自己定義AccessDecisionManager對應的bean -->
<bean id="accessDecisionManager"class="org.springframework.security.access.vote.AffirmativeBased">
<property name="decisionVoters">
<list>
<ref local="roleVoter"/>
</list>
</property>
</bean>
<bean id="roleVoter"
class="org.springframework.security.access.vote.RoleHierarchyVoter">
<constructor-arg ref="roleHierarchy" />
</bean>
<bean id="roleHierarchy"
class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
<property name="hierarchy"><!-- 角色繼承關系 -->
<value>
ROLE_ADMIN > ROLE_USER
</value>
</property>
</bean>
</beans>
在上述配置中我們就定義好了ROLE_ADMIN是繼承自ROLE_USER的,這樣ROLE_ADMIN將能夠訪問所有ROLE_USER可以訪問的資源。通過RoleHierarchyImpl的hierarchy屬性我們可以定義多個角色之間的繼承關系,如:
<bean id="roleHierarchy"
class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
<property name="hierarchy"><!-- 角色繼承關系 -->
<value>
ROLE_ADMIN > ROLE_USER
ROLE_A > ROLE_B
ROLE_B > ROLE_C
ROLE_C > ROLE_D
</value>
</property>
</bean>
在上述配置我們同時定義了ROLE_ADMIN繼承了ROLE_USER,ROLE_A繼承了ROLE_B,ROLE_B又繼承了ROLE_C,ROLE_C又繼承了ROLE_D,這樣ROLE_A將能訪問ROLE_B、ROLE_C和ROLE_D所能訪問的所有資源。
(注:本文是基於Spring Security3.1.6所寫)