這一期主要咱們將介紹訪問控制三劍客負責對受權規則作角色的組件——AccessDecisionVoter
接口。以及對Spring Security默認提供的幾個基礎AccessDecisionVoter
實現類作一個詳細的說明,最後咱們將會客製化一個基於時間的AccessDecisionVoter
實現用於實戰說明。java
AccessDecisionVoter
接口說明AccessDecisionVoter
們AccessDecisionVoter
AccessDecisionVoter
主要的職責就是對它所對應的訪問規則做出判斷,當前的訪問規則是否能夠獲得受權。
AccessDecisionVoter
接口的主要方法其實與以前的
AuthenticationProvider
很是的類似。
boolean supports(ConfigAttribute attribute);
int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);
複製代碼
ConfigAttribute
訪問規則是否支持;AccessDecisionVoter
投出本身的那一票,最終的投票結果是仍是要看當前的投票規則,好比是超過1/3仍是要過半數。而投票規則的判斷則是被放置了在了AccessDecisionManager
進行完成。int ACCESS_GRANTED = 1;
int ACCESS_ABSTAIN = 0;
int ACCESS_DENIED = -1;
複製代碼
AccessDecisionVoter
們經過上面對於AccessDecisionVoter
的基本介紹,咱們得知了一個設計上的大原則:AccessDecisionVoter
的實現是爲了知足對應規則ConfigAttribute
。大致上來講AccessDecisionVoter
是與ConfigAttribute
一一對應的。 讓咱們回一下在上一期咱們介紹的主要的幾種ConfigAttribute
實現:spring
AccessDecisionVoter
。
這邊咱們重點說一下在客製化場景下被利用的SecurityConfig配置和他默認的兩個AccessDecisionVoter
:@Secured("ROLE_USER")
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
複製代碼
咱們瞭解到了AccessDecisionVoter
和ConfigAttribute
的關聯關係是經過supports方法進行判斷,咱們分別對RoleVoter
和AuthenticatedVoter
的supports方法進行瀏覽:bash
RoleVoter RoleVoter
是Spring Security中默認基於角色規則的核心組件。在UserDetailsService中建立用戶咱們都會須要設置對用用戶的角色信息。在默認配置下用戶的角色信息都是以"ROLE_"+角色名的形式存儲的。 對應的在RoleVoter
的supports方法中會對錶達式是否以'ROLE_'開始做爲對應啓用規則的判斷。若是規則表達式是以ROLE_開始的,RoleVoter
則會去遍歷對用Authentication是否存在對應的角色,若是存在則返回經過,若是不存在則返回拒絕。cookie
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方法中,便會判斷當前的表達式是爲他所支持的三種認證方法的訪問控制:框架
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
結構、責任和Spring Security中提供的實現類有了一個基礎的瞭解後。咱們經過一個客製化的實例來增強這部分的理解。 咱們將客製化一個基於時間的訪問控制,在系統時間的分鐘數是奇數的狀況下才能夠被訪問,好比10點01分能夠訪問,可是10點02分則不能夠被訪問。ide
首先,咱們對訪問規則進行設計。咱們如同RoleVoter
與AuthenticatedVoter
同樣基於@Secured註解的表達式進行擴展。咱們擬定的規則名爲"MINUTE_ODD",當方法級被註解了@Secured("MINUTE_ODD")狀況下,表示當前方法只有在知足系統時間的分鐘數爲奇數下才能夠被訪問。post
接下來,咱們編寫一個MinuteBasedVoter
擴展AuthenticatedVoter
。ui
public class MinuteBasedVoter implements AccessDecisionVoter {
}
複製代碼
而後,咱們實現對應的suppors方法用於完成咱們對咱們擬定的規則的判斷。當入參ConfigAttribute 的表達式屬性與咱們預設的"MINUTE_ODD"一致時,那麼咱們便返回true告知框架,MinuteBasedVoter
須要對該規則進行vote的投票操做。this
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的投票核心業務邏輯完成:當時間爲奇數的時候則投贊同票,而在時間爲偶數的時候則投一張明確的反對票。spa
@Override
public int vote(Authentication authentication, Object object, Collection collection) {
if(LocalDateTime.now().getMinute() % 2 != 0){
return ACCESS_GRANTED;
}else{
return ACCESS_DENIED;
}
}
複製代碼
最後,說一下如何將新的AccessDecisionVoter
添加到現有的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
這一爲訪問控制提供核心判斷及投票的組件。同時也經過框架默認提供與客製化實現瞭解了其工做原理。 下一期咱們將最後一個核心組件AccessDecisionManager
是如何對全部AccessDecisionVoter
的投票結果進行彙總,以及如何以什麼評價規則告知框架最終的受權結果進行說明。 咱們下期再見。