授權架構

本節介紹適用於授權的 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

建議自定義 `AccessDecisionManager` 或 `AccessDecisionVoter` 的應用程式改為使用 AuthorizationManager

AuthorizationManagers 由 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 提供的最常見的 `AuthorizationManager` 是 AuthorityAuthorizationManager。它配置了一組給定的許可權,以便在當前 `Authentication` 上查詢。如果 `Authentication` 包含任何配置的許可權,它將返回肯定的 `AuthorizationDecision`。否則,它將返回否定的 `AuthorizationDecision`。

AuthenticatedAuthorizationManager

另一個管理器是 `AuthenticatedAuthorizationManager`。它可以用於區分匿名使用者、完全認證使用者和 remember-me 認證使用者。許多網站在 remember-me 認證下允許某些有限的訪問,但要求使用者透過登入確認身份才能獲得完全訪問許可權。

AuthorizationManagers

AuthorizationManagers 中還有一些有用的靜態工廠,用於將單個 `AuthorizationManager` 組合成更復雜的表示式。

自定義 Authorization Manager

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

您會在 Spring 網站上找到一篇部落格文章,描述瞭如何使用遺留的 AccessDecisionVoter 即時拒絕已被暫停賬戶的使用者的訪問。您可以透過實現 AuthorizationManager 來達到同樣的效果。

適配 AccessDecisionManager 和 AccessDecisionVoters

在 `AuthorizationManager` 之前,Spring Security 釋出了 AccessDecisionManagerAccessDecisionVoter

在某些情況下,例如遷移舊應用程式時,可能需要引入一個呼叫 AccessDecisionManagerAccessDecisionVoterAuthorizationManager

要呼叫現有的 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 中。

角色層級

一個常見的需求是,應用程式中的某個特定角色應自動“包含”其他角色。例如,在一個具有“admin”和“user”角色概念的應用程式中,您可能希望 admin 能夠執行普通使用者可以執行的所有操作。為了實現這一點,您可以確保所有 admin 使用者也被分配了“user”角色。或者,您可以修改每個需要“user”角色的訪問約束,使其也包含“admin”角色。如果您的應用程式中有很多不同的角色,這會變得相當複雜。

使用角色層級允許您配置哪些角色(或許可權)應該包含其他角色。`HttpSecurity#authorizeHttpRequests` 中支援基於過濾器的授權,透過 `DefaultMethodSecurityExpressionHandler` 支援 pre-post 註解、`SecuredAuthorizationManager` 支援 `@Secured` 以及 `Jsr250AuthorizationManager` 支援 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

AccessDecisionManager 由 `AbstractSecurityInterceptor` 呼叫,負責做出最終的訪問控制決定。`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` 是否可以處理傳入的 `ConfigAttribute`。`supports(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` 值,其可能值反映在 `AccessDecisionVoter` 的靜態欄位 `ACCESS_ABSTAIN`、`ACCESS_DENIED` 和 `ACCESS_GRANTED` 中。如果投票實現對授權決定沒有意見,則返回 `ACCESS_ABSTAIN`。如果它有意見,則必須返回 `ACCESS_DENIED` 或 `ACCESS_GRANTED`。

Spring Security 提供了三個具體的 `AccessDecisionManager` 實現來統計投票。`ConsensusBased` 實現根據非棄權投票的共識來授予或拒絕訪問。提供了屬性來控制投票相等或所有投票棄權時的行為。`AffirmativeBased` 實現如果收到一個或多個 `ACCESS_GRANTED` 投票則授予訪問許可權(換句話說,只要至少有一個授予投票,拒絕投票將被忽略)。與 `ConsensusBased` 實現一樣,有一個引數控制所有投票者棄權時的行為。`UnanimousBased` 提供者要求一致的 `ACCESS_GRANTED` 投票才能授予訪問許可權,忽略棄權。如果存在任何 `ACCESS_DENIED` 投票,則拒絕訪問。與其它實現一樣,有一個引數控制所有投票者棄權時的行為。

您可以實現一個自定義 `AccessDecisionManager`,以不同的方式統計投票。例如,來自特定 `AccessDecisionVoter` 的投票可能會獲得額外的權重,而來自特定投票者的拒絕投票可能會具有否決權。

RoleVoter

Spring Security 提供的最常用的 `AccessDecisionVoter` 是 RoleVoter,它將配置屬性視為角色名,並在使用者被分配了該角色時投票授予訪問許可權。

如果任何 `ConfigAttribute` 以 `ROLE_` 字首開頭,它就會投票。如果存在一個 `GrantedAuthority` 返回的 `String` 表示(來自 `getAuthority()` 方法)與一個或多個以 `ROLE_` 開頭的 `ConfigAttribute` 完全相等,它就投票授予訪問許可權。如果沒有任何以 `ROLE_` 開頭的 `ConfigAttribute` 完全匹配,`RoleVoter` 投票拒絕訪問。如果沒有 `ConfigAttribute` 以 `ROLE_` 開頭,投票者棄權。

AuthenticatedVoter

我們隱式看到的另一個投票者是 `AuthenticatedVoter`,它可以用於區分匿名使用者、完全認證使用者和 remember-me 認證使用者。許多網站在 remember-me 認證下允許某些有限的訪問,但要求使用者透過登入確認身份才能獲得完全訪問許可權。

當我們使用 `IS_AUTHENTICATED_ANONYMOUSLY` 屬性授予匿名訪問時,該屬性由 `AuthenticatedVoter` 處理。更多資訊,請參閱AuthenticatedVoter

自定義投票者

您還可以實現自定義 `AccessDecisionVoter`,並在其中放入幾乎任何您想要的訪問控制邏輯。它可能特定於您的應用程式(與業務邏輯相關),或者它可以實現一些安全管理邏輯。例如,在 Spring 網站上,您可以找到一篇部落格文章,描述瞭如何使用投票者即時拒絕已被暫停賬戶的使用者的訪問。

after invocation
圖 3. 呼叫後實現

與 Spring Security 的許多其他部分一樣,`AfterInvocationManager` 只有一個具體的實現,即 `AfterInvocationProviderManager`,它會輪詢 `AfterInvocationProvider` 的列表。每個 `AfterInvocationProvider` 都可以修改返回物件或丟擲 `AccessDeniedException`。實際上,多個提供者可以修改物件,因為前一個提供者的結果會傳遞給列表中的下一個提供者。

請注意,如果您使用 `AfterInvocationManager`,您仍然需要配置屬性來允許 `MethodSecurityInterceptor` 的 `AccessDecisionManager` 允許某項操作。如果您使用典型的 Spring Security 內建 `AccessDecisionManager` 實現,對於特定的安全方法呼叫,如果沒有定義配置屬性,將導致每個 `AccessDecisionVoter` 棄權投票。反過來,如果 `AccessDecisionManager` 屬性“allowIfAllAbstainDecisions”為 `false`,將丟擲 `AccessDeniedException`。您可以透過以下方法避免此潛在問題:(i) 將“allowIfAllAbstainDecisions”設定為 `true`(儘管通常不推薦這樣做),或者 (ii) 簡單地確保至少存在一個配置屬性,`AccessDecisionVoter` 會投票授予訪問許可權。後一種(推薦)方法通常透過 `ROLE_USER` 或 `ROLE_AUTHENTICATED` 配置屬性來實現。