授權架構

本節描述了適用於授權的 Spring Security 架構。

許可權

Authentication 討論了所有 Authentication 實現如何儲存 GrantedAuthority 物件的列表。這些物件表示授予主體(principal)的許可權。GrantedAuthority 物件由 AuthenticationManager 插入到 Authentication 物件中,並在稍後由 AuthorizationManager 例項在做出授權決策時讀取。

GrantedAuthority 介面只有一個方法

String getAuthority();

此方法由 AuthorizationManager 例項用於獲取 GrantedAuthority 的精確 String 表示。透過返回 String 表示,GrantedAuthority 可以很容易地被大多數 AuthorizationManager 實現“讀取”。如果 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 介面包含兩個方法

AuthorizationResult authorize(Supplier<Authentication> authentication, Object secureObject);

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

AuthorizationManagercheck 方法被傳遞了所有做出授權決策所需的相關資訊。特別是,傳遞安全 Object 使得可以檢查實際安全物件呼叫中包含的引數。例如,假設安全物件是 MethodInvocation。可以很容易地查詢 MethodInvocation 以獲取任何 Customer 引數,然後在 AuthorizationManager 中實現某種安全邏輯,以確保主體被允許對該客戶進行操作。如果授予訪問許可權,實現預期會返回一個肯定的 AuthorizationDecision;如果拒絕訪問許可權,則返回一個否定的 AuthorizationDecision;當不做出決策時,則返回一個空的 AuthorizationDecision

verify 呼叫 authorize,然後在 AuthorizationDecision 為負的情況下丟擲 AccessDeniedException

基於委託的 AuthorizationManager 實現

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

RequestMatcherDelegatingAuthorizationManager 將請求與最合適的委託 AuthorizationManager 匹配。對於方法安全性,您可以使用 AuthorizationManagerBeforeMethodInterceptorAuthorizationManagerAfterMethodInterceptor

Authorization Manager 實現 演示了相關類。

authorizationhierarchy
圖 1. 授權管理器實現

使用這種方法,可以就授權決策對 AuthorizationManager 實現的組合進行輪詢。

AuthorityAuthorizationManager

Spring Security 中最常見的 AuthorizationManagerAuthorityAuthorizationManager。它配置了一組給定的許可權,以便在當前 Authentication 上查詢。如果 Authentication 包含任何已配置的許可權,它將返回肯定的 AuthorizationDecision。否則,它將返回否定的 AuthorizationDecision

AuthenticatedAuthorizationManager

另一個管理器是 AuthenticatedAuthorizationManager。它可以用來區分匿名使用者、完全認證使用者和“記住我”認證使用者。許多網站在“記住我”認證下允許某些有限訪問,但要求使用者透過登入來確認其身份以獲得完全訪問。

建立 AuthorizationManager 例項

AuthorizationManagerFactory 介面(在 Spring Security 7.0 中引入)用於在基於請求的基於方法的授權元件中建立通用的 AuthorizationManager。以下是 AuthorizationManagerFactory 介面的草圖

public interface AuthorizationManagerFactory<T> {
	AuthorizationManager<T> permitAll();
	AuthorizationManager<T> denyAll();
	AuthorizationManager<T> hasRole(String role);
	AuthorizationManager<T> hasAnyRole(String... roles);
	AuthorizationManager<T> hasAllRoles(String... roles);
	AuthorizationManager<T> hasAuthority(String authority);
	AuthorizationManager<T> hasAnyAuthority(String... authorities);
	AuthorizationManager<T> hasAllAuthorities(String... authorities);
	AuthorizationManager<T> authenticated();
	AuthorizationManager<T> fullyAuthenticated();
	AuthorizationManager<T> rememberMe();
	AuthorizationManager<T> anonymous();
}

預設實現是 DefaultAuthorizationManagerFactory,它允許自定義提供給工廠建立的 AuthorizationManagerrolePrefix(預設為 "ROLE_")、RoleHierarchyAuthenticationTrustManager

為了自定義 Spring Security 使用的預設例項,只需釋出一個 bean,如以下示例所示

  • Java

  • Kotlin

  • Xml

@Bean
<T> AuthorizationManagerFactory<T> authorizationManagerFactory() {
	DefaultAuthorizationManagerFactory<T> authorizationManagerFactory =
			new DefaultAuthorizationManagerFactory<>();
	authorizationManagerFactory.setTrustResolver(getAuthenticationTrustResolver());
	authorizationManagerFactory.setRoleHierarchy(getRoleHierarchy());
	authorizationManagerFactory.setRolePrefix("role_");

	return authorizationManagerFactory;
}
@Bean
fun <T> authorizationManagerFactory(): AuthorizationManagerFactory<T> {
    val authorizationManagerFactory = DefaultAuthorizationManagerFactory<T>()
    authorizationManagerFactory.setTrustResolver(getAuthenticationTrustResolver())
    authorizationManagerFactory.setRoleHierarchy(getRoleHierarchy())
    authorizationManagerFactory.setRolePrefix("role_")

    return authorizationManagerFactory
}
<b:bean id="authorizationManagerFactory" class="org.springframework.security.authorization.DefaultAuthorizationManagerFactory">
	<b:property name="trustResolver" ref="authenticationTrustResolver"/>
	<b:property name="roleHierarchy" ref="roleHierarchy"/>
	<b:property name="rolePrefix" value="role_"/>
</b:bean>
還可以透過提供具體的引數化型別而不是泛型型別來在 Spring Security 中定位此工廠的特定用法。請參閱文件的基於請求的基於方法的部分中的每個示例。

除了簡單地自定義 AuthorizationManagerFactory 的預設例項之外,您還可以提供自己的實現來完全自定義工廠建立的例項並提供自己的實現。

實際介面為所有工廠方法提供了預設實現,這允許自定義實現只實現需要自定義的方法。

AuthorizationManagers

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

自定義授權管理器

顯然,您還可以實現自定義的 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 AuthorizationResult authorize(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 AuthorizationResult authorize(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”角色概念的應用程式中,您可能希望管理員能夠執行普通使用者可以執行的所有操作。為此,您可以確保所有管理員使用者也被分配了“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 包含一些遺留元件。由於它們尚未移除,因此包含文件供歷史參考。建議的替代方案在上方。

訪問遺留授權元件時,請務必同時包含 spring-security-access 依賴項,如下所示

  • Maven

  • Gradle

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-access</artifactId>
</dependency>
implementation('org.springframework.security:spring-security-access')

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,其可能的值反映在 AccessDecisionVoter 靜態欄位中,名為 ACCESS_ABSTAINACCESS_DENIEDACCESS_GRANTED。如果一個投票實現對授權決策沒有意見,則返回 ACCESS_ABSTAIN。如果它有意見,則必須返回 ACCESS_DENIEDACCESS_GRANTED

Spring Security 提供了三個具體的 AccessDecisionManager 實現來統計投票。ConsensusBased 實現根據非棄權票的共識授予或拒絕訪問。提供了屬性來控制投票相等或所有投票都棄權時的行為。AffirmativeBased 實現在一個或多個 ACCESS_GRANTED 票被收到時授予訪問許可權(換句話說,只要至少有一張授予票,拒絕票就會被忽略)。與 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 配置屬性實現。

© . This site is unofficial and not affiliated with VMware.