1、三種表決方式,默認是 一票制AffirmativeBased
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是否支持對應的受保護對象類型。
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。
配置方式
投票器列表 - decisionVoters屬性
默認的AccessDecisionManager要求我們配置投票器的一個屬性,decisionVoters屬性,它們將會在認證決策時用到。
在我們不聲明AccessDecisionManager時,decisionVoters屬性是自動配置的,配置的是1-多個投票器。
2、兩種默認投票器:RoleVoter和AuthenticatedVoter
2.1、RoleVoter
RoleVoter是Spring Security內置的一個AccessDecisionVoter,其會將ConfigAttribute簡單的看作是一個角色名稱,在投票的時如果擁有該角色即投贊成票。
如果ConfigAttribute是以“ROLE_”開頭的,則將使用RoleVoter進行投票。
當用戶擁有的權限中有一個或多個能匹配受保護對象配置的以“ROLE_”開頭的ConfigAttribute時其將投贊成票;
如果用戶擁有的權限中沒有一個能匹配受保護對象配置的以“ROLE_”開頭的ConfigAttribute,則RoleVoter將投反對票;
如果受保護對象配置的ConfigAttribute中沒有以“ROLE_”開頭的,則RoleVoter將棄權。
2.2、AuthenticatedVoter
AuthenticatedVoter也是Spring Security內置的一個AccessDecisionVoter實現。其主要用來區分匿名用戶、通過Remember-Me認證的用戶和完全認證的用戶。
完全認證的用戶是指由系統提供的登錄入口進行成功登錄認證的用戶。
AuthenticatedVoter可以處理的ConfigAttribute有IS_AUTHENTICATED_FULLY、IS_AUTHENTICATED_REMEMBERED和IS_AUTHENTICATED_ANONYMOUSLY。
如果ConfigAttribute不在這三者范圍之內,則AuthenticatedVoter將棄權。
否則將視ConfigAttribute而定,
如果ConfigAttribute為 IS_AUTHENTICATED_ANONYMOUSLY,則不管用戶是匿名的還是已經認證的都將投贊成票;
如果ConfigAttribute為 IS_AUTHENTICATED_REMEMBERED,則僅當用戶是由Remember-Me自動登錄,或者是通過登錄入口進行登錄認證時才會投贊成票,否則將投反對票;
如果ConfigAttribute為 IS_AUTHENTICATED_FULLY時, 則僅當用戶是通過登錄入口進行登錄的才會投贊成票,否則將投反對票。
AuthenticatedVoter是通過AuthenticationTrustResolver的isAnonymous()方法和isRememberMe()方法來判斷SecurityContextHolder持有的Authentication是否為AnonymousAuthenticationToken或RememberMeAuthenticationToken的,
即是否為IS_AUTHENTICATED_ANONYMOUSLY和IS_AUTHENTICATED_REMEMBERED。
2.3、支持自定義投票器:實現AccessDecisionVoter接口並在配置中添加我們的實現
3、使用 Spring 表達式語言配置訪問控制
3.1、配置 use-expressions="true"
添加后將要修改用來進行攔截器規則聲明的 access 屬性,改為 SpEL 表達式。 SpEL 允許使用特定的訪問控制規則表達式語言。
與簡單的字符串如 ROLE_USER 不同,配置文件可以指明表達式語言觸發方法調用、引用系統屬性、計算機值等等。
SpEL 的語法與其他的表達式語言很類似,如在 Tapestry 等框架中用到的 Object Graph Notation Language (OGNL) ,以及用於 JSP 和 JSF 的 Unified Expression Language 。
3.2、使用表達式后的role處理
如果你通過使用 use-expressions 屬性啟用了 SpEL 表達式訪問控制,將會使得自動配置的 RoleVoter 實效,后者能夠使用角色的聲明,正如在前面的例子所見到的那樣:
<intercept-url pattern="/*" access="ROLE_USER"/> |
這意味着如果你僅僅想通過角色來過濾請求的話,訪問控制聲明必要要進行修改。幸運的的是,這已經被充分考慮過了,一個 SpEL 綁定的方法 hasRole 能夠檢查角色。
3.3、使用表達式的一些例子