授權架構

本節說明適用於授權的 Spring Security 架構。

權限

Authentication 討論所有 Authentication 實作如何儲存 GrantedAuthority 物件的清單。這些代表已授予主體的權限。GrantedAuthority 物件由 AuthenticationManager 插入 Authentication 物件中,並在稍後由 AccessDecisionManager 執行個體讀取,以進行授權決策。

GrantedAuthority 介面只有一個方法

String getAuthority();

此方法由 AuthorizationManager 執行個體使用,以取得 GrantedAuthority 的精確 String 表示法。藉由傳回 String 形式的表示法,大多數 AuthorizationManager 實作可以輕鬆「讀取」GrantedAuthority。如果 GrantedAuthority 無法精確地表示為 String,則 GrantedAuthority 會被視為「複雜」,且 getAuthority() 必須傳回 null

複雜 GrantedAuthority 的範例是儲存適用於不同客戶帳戶號碼的操作和權限閾值清單的實作。將此複雜 GrantedAuthority 表示為 String 將非常困難。因此,getAuthority() 方法應傳回 null。這向任何 AuthorizationManager 指出,它需要支援特定的 GrantedAuthority 實作才能理解其內容。

Spring Security 包含一個具體的 GrantedAuthority 實作:SimpleGrantedAuthority。此實作讓任何使用者指定的 String 都可以轉換為 GrantedAuthority。安全性架構隨附的所有 AuthenticationProvider 執行個體都使用 SimpleGrantedAuthority 來填入 Authentication 物件。

依預設,基於角色的授權規則包含 ROLE_ 作為前綴。這表示,如果有一個授權規則要求安全性內容具有 "USER" 角色,則 Spring Security 依預設會尋找傳回 "ROLE_USER" 的 GrantedAuthority#getAuthority

您可以使用 GrantedAuthorityDefaults 自訂此設定。GrantedAuthorityDefaults 的存在是為了允許自訂用於基於角色的授權規則的前綴。

您可以透過公開 GrantedAuthorityDefaults bean 來組態授權規則以使用不同的前綴,如下所示

自訂 MethodSecurityExpressionHandler
  • Java

  • Kotlin

  • Xml

@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
	return new GrantedAuthorityDefaults("MYPREFIX_");
}
companion object {
	@Bean
	fun grantedAuthorityDefaults() : GrantedAuthorityDefaults {
		return GrantedAuthorityDefaults("MYPREFIX_");
	}
}
<bean id="grantedAuthorityDefaults" class="org.springframework.security.config.core.GrantedAuthorityDefaults">
	<constructor-arg value="MYPREFIX_"/>
</bean>

您可以使用 static 方法公開 GrantedAuthorityDefaults,以確保 Spring 在初始化 Spring Security 的方法安全性 @Configuration 類別之前發布它

調用處理

Spring Security 提供攔截器,用於控制對安全物件的存取,例如方法調用或 Web 請求。關於是否允許繼續調用的調用前決策由 AuthorizationManager 執行個體制定。關於給定值是否可以傳回的調用後決策也由 AuthorizationManager 執行個體制定。

AuthorizationManager

AuthorizationManager 取代了 AccessDecisionManagerAccessDecisionVoter

鼓勵自訂 AccessDecisionManagerAccessDecisionVoter 的應用程式變更為使用 AuthorizationManager

AuthorizationManager 由 Spring Security 的基於請求基於方法基於訊息的授權組件呼叫,並負責制定最終的存取控制決策。AuthorizationManager 介面包含兩種方法

AuthorizationDecision check(Supplier<Authentication> authentication, Object secureObject);

default void verify(Supplier<Authentication> authentication, Object secureObject)
        throws AccessDeniedException {
    // ...
}

AuthorizationManagercheck 方法會傳遞它需要的所有相關資訊,以便制定授權決策。特別是,傳遞安全 Object 允許檢查實際安全物件調用中包含的那些引數。例如,假設安全物件是 MethodInvocation。查詢 MethodInvocation 以取得任何 Customer 引數將很容易,然後在 AuthorizationManager 中實作某種安全性邏輯,以確保主體被允許對該客戶執行操作。如果授予存取權,則預期實作會傳回肯定的 AuthorizationDecision;如果拒絕存取權,則傳回否定的 AuthorizationDecision;以及在棄權制定決策時傳回 null AuthorizationDecision

verify 呼叫 check,然後在否定的 AuthorizationDecision 的情況下拋出 AccessDeniedException

基於委派的 AuthorizationManager 實作

雖然使用者可以實作自己的 AuthorizationManager 來控制授權的所有方面,但 Spring Security 隨附一個委派 AuthorizationManager,它可以與個別的 AuthorizationManager 協作。

RequestMatcherDelegatingAuthorizationManager 將請求與最合適的委派 AuthorizationManager 進行比對。對於方法安全性,您可以使用 AuthorizationManagerBeforeMethodInterceptorAuthorizationManagerAfterMethodInterceptor

Authorization Manager 實作說明了相關的類別。

authorizationhierarchy
圖 1. Authorization Manager 實作

使用此方法,可以輪詢 AuthorizationManager 實作的組合以取得授權決策。

AuthorityAuthorizationManager

Spring Security 提供的最常見 AuthorizationManagerAuthorityAuthorizationManager。它組態為一組給定的權限,以在目前的 Authentication 上尋找。如果 Authentication 包含任何已組態的權限,它將傳回肯定的 AuthorizationDecision。否則,它將傳回否定的 AuthorizationDecision

AuthenticatedAuthorizationManager

另一個管理員是 AuthenticatedAuthorizationManager。它可用於區分匿名、完全驗證和「記住我」驗證的使用者。許多網站允許在「記住我」驗證下進行某些有限的存取,但要求使用者透過登入來確認其身分以取得完全存取權。

AuthorizationManagers

AuthorizationManagers 中,還有一些有用的靜態工廠,用於將個別的 AuthorizationManager 組合成更複雜的運算式。

自訂 Authorization Manager

顯然,您也可以實作自訂 AuthorizationManager,並且可以在其中放入您想要的任何存取控制邏輯。它可能特定於您的應用程式(與業務邏輯相關),或者它可能實作一些安全性管理邏輯。例如,您可以建立一個可以查詢 Open Policy Agent 或您自己的授權資料庫的實作。

您會在 Spring 網站上找到一篇部落格文章,其中說明如何使用舊版 AccessDecisionVoter 即時拒絕存取帳戶已暫停的使用者。您可以透過實作 AuthorizationManager 來達到相同的結果。

調整 AccessDecisionManager 和 AccessDecisionVoters

AuthorizationManager 之前,Spring Security 發布了 AccessDecisionManagerAccessDecisionVoter

在某些情況下,例如遷移較舊的應用程式,可能希望引入一個 AuthorizationManager,它會調用 AccessDecisionManagerAccessDecisionVoter

若要呼叫現有的 AccessDecisionManager,您可以執行

調整 AccessDecisionManager
  • Java

@Component
public class AccessDecisionManagerAuthorizationManagerAdapter implements AuthorizationManager {
    private final AccessDecisionManager accessDecisionManager;
    private final SecurityMetadataSource securityMetadataSource;

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, Object object) {
        try {
            Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
            this.accessDecisionManager.decide(authentication.get(), object, attributes);
            return new AuthorizationDecision(true);
        } catch (AccessDeniedException ex) {
            return new AuthorizationDecision(false);
        }
    }

    @Override
    public void verify(Supplier<Authentication> authentication, Object object) {
        Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
        this.accessDecisionManager.decide(authentication.get(), object, attributes);
    }
}

然後將其連接到您的 SecurityFilterChain

或者,僅呼叫 AccessDecisionVoter,您可以執行

調整 AccessDecisionVoter
  • Java

@Component
public class AccessDecisionVoterAuthorizationManagerAdapter implements AuthorizationManager {
    private final AccessDecisionVoter accessDecisionVoter;
    private final SecurityMetadataSource securityMetadataSource;

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, Object object) {
        Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
        int decision = this.accessDecisionVoter.vote(authentication.get(), object, attributes);
        switch (decision) {
        case ACCESS_GRANTED:
            return new AuthorizationDecision(true);
        case ACCESS_DENIED:
            return new AuthorizationDecision(false);
        }
        return null;
    }
}

然後將其連接到您的 SecurityFilterChain

階層式角色

應用程式中的特定角色應自動「包含」其他角色,這是一個常見的要求。例如,在具有「管理員」和「使用者」角色概念的應用程式中,您可能希望管理員能夠執行一般使用者可以執行的所有操作。若要達成此目的,您可以確保所有管理員使用者也獲指派「使用者」角色。或者,您可以修改每個需要「使用者」角色的存取限制,以也包含「管理員」角色。如果您在應用程式中有很多不同的角色,這可能會變得非常複雜。

角色階層的使用可讓您組態哪些角色(或權限)應包含其他角色。HttpSecurity#authorizeHttpRequests 中的基於篩選器的授權,以及透過 DefaultMethodSecurityExpressionHandler 用於 pre-post 註釋、SecuredAuthorizationManager 用於 @SecuredJsr250AuthorizationManager 用於 JSR-250 註釋的基於方法的授權都支援此功能。您可以透過以下方式一次組態所有這些行為

階層式角色組態
  • Java

  • Xml

@Bean
static RoleHierarchy roleHierarchy() {
    return RoleHierarchyImpl.withDefaultRolePrefix()
        .role("ADMIN").implies("STAFF")
        .role("STAFF").implies("USER")
        .role("USER").implies("GUEST")
        .build();
}

// and, if using pre-post method security also add
@Bean
static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
	DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
	expressionHandler.setRoleHierarchy(roleHierarchy);
	return expressionHandler;
}
<bean id="roleHierarchy"
		class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl" factory-method="fromHierarchy">
	<constructor-arg>
		<value>
			ROLE_ADMIN > ROLE_STAFF
			ROLE_STAFF > ROLE_USER
			ROLE_USER > ROLE_GUEST
		</value>
	</constructor-arg>
</bean>

<!-- and, if using method security also add -->
<bean id="methodSecurityExpressionHandler"
        class="org.springframework.security.access.expression.method.MethodSecurityExpressionHandler">
    <property ref="roleHierarchy"/>
</bean>

在這裡,我們在階層 ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST 中有四個角色。使用 ROLE_ADMIN 驗證的使用者在針對任何基於篩選器或基於方法的規則評估安全性限制時,其行為就像他們擁有所有四個角色一樣。

> 符號可以視為表示「包含」。

角色階層提供了一種簡便的方法,可簡化應用程式的存取控制組態資料,和/或減少您需要指派給使用者的權限數量。對於更複雜的需求,您可能希望在應用程式需要的特定存取權限與指派給使用者的角色之間定義邏輯對應,並在載入使用者資訊時在這兩者之間進行轉換。

舊版授權組件

Spring Security 包含一些舊版組件。由於它們尚未移除,因此包含文件以供歷史參考。它們建議的替代方案在上面。

AccessDecisionManager

AccessDecisionManagerAbstractSecurityInterceptor 呼叫,並負責制定最終的存取控制決策。AccessDecisionManager 介面包含三種方法

void decide(Authentication authentication, Object secureObject,
	Collection<ConfigAttribute> attrs) throws AccessDeniedException;

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

AccessDecisionManagerdecide 方法會傳遞它需要的所有相關資訊,以便制定授權決策。特別是,傳遞安全 Object 可讓檢查實際安全物件調用中包含的那些引數。例如,假設安全物件是 MethodInvocation。您可以查詢 MethodInvocation 以取得任何 Customer 引數,然後在 AccessDecisionManager 中實作某種安全性邏輯,以確保主體被允許對該客戶執行操作。如果拒絕存取權,則預期實作會拋出 AccessDeniedException

supports(ConfigAttribute) 方法由 AbstractSecurityInterceptor 在啟動時呼叫,以判斷 AccessDecisionManager 是否可以處理傳遞的 ConfigAttributesupports(Class) 方法由安全性攔截器實作呼叫,以確保組態的 AccessDecisionManager 支援安全性攔截器呈現的安全物件類型。

基於投票的 AccessDecisionManager 實作

雖然使用者可以實作自己的 AccessDecisionManager 來控制授權的所有方面,但 Spring Security 包含多個基於投票的 AccessDecisionManager 實作。投票決策管理員說明了相關的類別。

下圖顯示了 AccessDecisionManager 介面

access decision voting
圖 2. 投票決策管理員

透過使用此方法,會輪詢一系列 AccessDecisionVoter 實作以取得授權決策。然後,AccessDecisionManager 會根據其對投票的評估,決定是否拋出 AccessDeniedException

AccessDecisionVoter 介面有三種方法

int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs);

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

具體的實作會傳回 int,可能的值反映在名為 ACCESS_ABSTAINACCESS_DENIEDACCESS_GRANTEDAccessDecisionVoter 靜態欄位中。如果投票實作對授權決策沒有意見,則傳回 ACCESS_ABSTAIN。如果它有意見,則必須傳回 ACCESS_DENIEDACCESS_GRANTED

Spring Security 提供了三個具體的 AccessDecisionManager 實作來統計投票。ConsensusBased 實作根據非棄權投票的共識授予或拒絕存取權。提供屬性來控制在票數相等或所有投票都棄權時的行為。如果收到一個或多個 ACCESS_GRANTED 投票,則 AffirmativeBased 實作會授予存取權(換句話說,只要至少有一個授予投票,就會忽略拒絕投票)。與 ConsensusBased 實作類似,有一個參數可以控制在所有投票者都棄權時的行為。UnanimousBased 提供者預期一致的 ACCESS_GRANTED 投票才能授予存取權,忽略棄權。如果有任何 ACCESS_DENIED 投票,它會拒絕存取權。與其他實作類似,有一個參數可以控制在所有投票者都棄權時的行為。

您可以實作自訂 AccessDecisionManager,以不同方式統計投票。例如,來自特定 AccessDecisionVoter 的投票可能會獲得額外的權重,而來自特定投票者的拒絕投票可能會產生否決效果。

RoleVoter

Spring Security 提供的最常用 AccessDecisionVoterRoleVoter,它將組態屬性視為角色名稱,並在使用者已被指派該角色時投票以授予存取權。

如果有任何 ConfigAttributeROLE_ 前綴開頭,它就會投票。如果有一個 GrantedAuthority 傳回一個 String 表示法(來自 getAuthority() 方法),該表示法與一個或多個以 ROLE_ 前綴開頭的 ConfigAttributes 完全相等,則它會投票以授予存取權。如果沒有以 ROLE_ 開頭的任何 ConfigAttribute 完全符合,則 RoleVoter 會投票拒絕存取權。如果沒有 ConfigAttributeROLE_ 開頭,則投票者會棄權。

AuthenticatedVoter

我們隱含地看到的另一個投票者是 AuthenticatedVoter,它可用於區分匿名、完全驗證和「記住我」驗證的使用者。許多網站允許在「記住我」驗證下進行某些有限的存取,但要求使用者透過登入來確認其身分以取得完全存取權。

當我們使用 IS_AUTHENTICATED_ANONYMOUSLY 屬性來授予匿名存取權時,此屬性正在由 AuthenticatedVoter 處理。如需更多資訊,請參閱 AuthenticatedVoter

自訂投票者

您也可以實作自訂 AccessDecisionVoter,並在其中放入您想要的任何存取控制邏輯。它可能特定於您的應用程式(與業務邏輯相關),或者它可能實作一些安全性管理邏輯。例如,在 Spring 網站上,您可以找到一篇部落格文章,其中說明如何使用投票者即時拒絕存取帳戶已暫停的使用者。

after invocation
圖 3. 調用後實作

與 Spring Security 的許多其他部分一樣,AfterInvocationManager 只有一個具體的實作 AfterInvocationProviderManager,它輪詢 AfterInvocationProvider 的清單。每個 AfterInvocationProvider 都被允許修改傳回物件或拋出 AccessDeniedException。實際上,多個提供者可以修改物件,因為先前提供者的結果會傳遞到清單中的下一個提供者。

請注意,如果您正在使用 AfterInvocationManager,您仍然需要允許 MethodSecurityInterceptorAccessDecisionManager 允許操作的組態屬性。如果您正在使用典型的 Spring Security 隨附的 AccessDecisionManager 實作,則對於特定的安全方法調用未定義任何組態屬性將導致每個 AccessDecisionVoter 棄權投票。反過來,如果 AccessDecisionManager 屬性「allowIfAllAbstainDecisions」為 false,則會拋出 AccessDeniedException。您可以透過 (i) 將「allowIfAllAbstainDecisions」設定為 true(儘管通常不建議這樣做)或 (ii) 僅確保至少有一個組態屬性,AccessDecisionVoter 將投票以授予存取權,來避免此潛在問題。後者(建議的)方法通常是透過 ROLE_USERROLE_AUTHENTICATED 組態屬性來實現。