Spring Security教程 Vol 8. AccessDecisionVoter組件介紹

第八期 AccessDecisionVoter組件介紹

這一期主要咱們將介紹訪問控制三劍客負責對受權規則作角色的組件——AccessDecisionVoter接口。以及對Spring Security默認提供的幾個基礎AccessDecisionVoter實現類作一個詳細的說明,最後咱們將會客製化一個基於時間的AccessDecisionVoter實現用於實戰說明。java

  • AccessDecisionVoter接口說明
  • Spring Security的AccessDecisionVoter
  • 客製化實例:基於時間的AccessDecisionVoter

1、AccessDecisionVoter接口說明

AccessDecisionVoter接口說明
AccessDecisionVoter主要的職責就是對它所對應的訪問規則做出判斷,當前的訪問規則是否能夠獲得受權。 AccessDecisionVoter接口的主要方法其實與以前的 AuthenticationProvider很是的類似。

boolean supports(ConfigAttribute attribute);

	int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);
複製代碼
  • supports方法用於判斷對於當前ConfigAttribute訪問規則是否支持;
  • 若是支持的狀況下,vote方法對其進行判斷投票返回對應的受權結果。 最終的受權結果一共有三種,分別是贊成、棄權和反對。說實話這個規則和聯合國安理會投票差很少性質。當前一個訪問可能存在多個規則的狀況下,每個AccessDecisionVoter投出本身的那一票,最終的投票結果是仍是要看當前的投票規則,好比是超過1/3仍是要過半數。而投票規則的判斷則是被放置了在了AccessDecisionManager進行完成。
int ACCESS_GRANTED = 1;
	int ACCESS_ABSTAIN = 0;
	int ACCESS_DENIED = -1;
複製代碼

2、 Spring Security的AccessDecisionVoter

經過上面對於AccessDecisionVoter的基本介紹,咱們得知了一個設計上的大原則:AccessDecisionVoter的實現是爲了知足對應規則ConfigAttribute。大致上來講AccessDecisionVoter是與ConfigAttribute一一對應的。 讓咱們回一下在上一期咱們介紹的主要的幾種ConfigAttribute實現:spring

  • 基於Web表達式的WebExpressionConfigAttribute
  • 基於@Secured註解的SecurityConfig
  • 基於@Pre-@Post註解的PostInvocationExpressionAttribute 咱們能夠在下圖中輕鬆的找到他門對應的AccessDecisionVoter
    主要的AccessDecisionVoter
    這邊咱們重點說一下在客製化場景下被利用的SecurityConfig配置和他默認的兩個AccessDecisionVoter:
  • RoleVoter
  • AuthenticatedVoter 首先,咱們來回憶下SecurityConfig的使用形式,即利用@Secured註解編寫一個表達式:
@Secured("ROLE_USER")
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
複製代碼

咱們瞭解到了AccessDecisionVoterConfigAttribute的關聯關係是經過supports方法進行判斷,咱們分別對RoleVoterAuthenticatedVoter的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方法中,便會判斷當前的表達式是爲他所支持的三種認證方法的訪問控制:框架

  • 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;
		}
	}
}
複製代碼

3、 客製化實例:基於時間的AccessDecisionVoter

對於AccessDecisionVoter結構、責任和Spring Security中提供的實現類有了一個基礎的瞭解後。咱們經過一個客製化的實例來增強這部分的理解。 咱們將客製化一個基於時間的訪問控制,在系統時間的分鐘數是奇數的狀況下才能夠被訪問,好比10點01分能夠訪問,可是10點02分則不能夠被訪問。ide

設計規則

首先,咱們對訪問規則進行設計。咱們如同RoleVoterAuthenticatedVoter同樣基於@Secured註解的表達式進行擴展。咱們擬定的規則名爲"MINUTE_ODD",當方法級被註解了@Secured("MINUTE_ODD")狀況下,表示當前方法只有在知足系統時間的分鐘數爲奇數下才能夠被訪問。post

客製化MinuteBasedVoter

接下來,咱們編寫一個MinuteBasedVoter擴展AuthenticatedVoterui

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;
        }
    }
複製代碼

Java Config配置

最後,說一下如何將新的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的投票結果進行彙總,以及如何以什麼評價規則告知框架最終的受權結果進行說明。 咱們下期再見。

相關文章
相關標籤/搜索