授權架構
本節說明適用於授權的 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
物件。
您可以使用 GrantedAuthorityDefaults
自訂此設定。GrantedAuthorityDefaults
的存在是為了允許自訂用於基於角色的授權規則的前綴。
您可以透過公開 GrantedAuthorityDefaults
bean 來組態授權規則以使用不同的前綴,如下所示
-
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>
您可以使用 |
調用處理
Spring Security 提供攔截器,用於控制對安全物件的存取,例如方法調用或 Web 請求。關於是否允許繼續調用的調用前決策由 AuthorizationManager
執行個體制定。關於給定值是否可以傳回的調用後決策也由 AuthorizationManager
執行個體制定。
AuthorizationManager
AuthorizationManager
取代了 AccessDecisionManager
和 AccessDecisionVoter
。
鼓勵自訂 AccessDecisionManager
或 AccessDecisionVoter
的應用程式變更為使用 AuthorizationManager
。
AuthorizationManager
由 Spring Security 的基於請求、基於方法和基於訊息的授權組件呼叫,並負責制定最終的存取控制決策。AuthorizationManager
介面包含兩種方法
AuthorizationDecision check(Supplier<Authentication> authentication, Object secureObject);
default void verify(Supplier<Authentication> authentication, Object secureObject)
throws AccessDeniedException {
// ...
}
AuthorizationManager
的 check
方法會傳遞它需要的所有相關資訊,以便制定授權決策。特別是,傳遞安全 Object
允許檢查實際安全物件調用中包含的那些引數。例如,假設安全物件是 MethodInvocation
。查詢 MethodInvocation
以取得任何 Customer
引數將很容易,然後在 AuthorizationManager
中實作某種安全性邏輯,以確保主體被允許對該客戶執行操作。如果授予存取權,則預期實作會傳回肯定的 AuthorizationDecision
;如果拒絕存取權,則傳回否定的 AuthorizationDecision
;以及在棄權制定決策時傳回 null AuthorizationDecision
。
verify
呼叫 check
,然後在否定的 AuthorizationDecision
的情況下拋出 AccessDeniedException
。
基於委派的 AuthorizationManager 實作
雖然使用者可以實作自己的 AuthorizationManager
來控制授權的所有方面,但 Spring Security 隨附一個委派 AuthorizationManager
,它可以與個別的 AuthorizationManager
協作。
RequestMatcherDelegatingAuthorizationManager
將請求與最合適的委派 AuthorizationManager
進行比對。對於方法安全性,您可以使用 AuthorizationManagerBeforeMethodInterceptor
和 AuthorizationManagerAfterMethodInterceptor
。
Authorization Manager 實作說明了相關的類別。
使用此方法,可以輪詢 AuthorizationManager
實作的組合以取得授權決策。
AuthorityAuthorizationManager
Spring Security 提供的最常見 AuthorizationManager
是 AuthorityAuthorizationManager
。它組態為一組給定的權限,以在目前的 Authentication
上尋找。如果 Authentication
包含任何已組態的權限,它將傳回肯定的 AuthorizationDecision
。否則,它將傳回否定的 AuthorizationDecision
。
AuthenticatedAuthorizationManager
另一個管理員是 AuthenticatedAuthorizationManager
。它可用於區分匿名、完全驗證和「記住我」驗證的使用者。許多網站允許在「記住我」驗證下進行某些有限的存取,但要求使用者透過登入來確認其身分以取得完全存取權。
AuthorizationManagers
在 AuthorizationManagers
中,還有一些有用的靜態工廠,用於將個別的 AuthorizationManager
組合成更複雜的運算式。
自訂 Authorization Manager
顯然,您也可以實作自訂 AuthorizationManager
,並且可以在其中放入您想要的任何存取控制邏輯。它可能特定於您的應用程式(與業務邏輯相關),或者它可能實作一些安全性管理邏輯。例如,您可以建立一個可以查詢 Open Policy Agent 或您自己的授權資料庫的實作。
您會在 Spring 網站上找到一篇部落格文章,其中說明如何使用舊版 AccessDecisionVoter 即時拒絕存取帳戶已暫停的使用者。您可以透過實作 AuthorizationManager 來達到相同的結果。 |
調整 AccessDecisionManager 和 AccessDecisionVoters
在 AuthorizationManager
之前,Spring Security 發布了 AccessDecisionManager
和 AccessDecisionVoter
。
在某些情況下,例如遷移較舊的應用程式,可能希望引入一個 AuthorizationManager
,它會調用 AccessDecisionManager
或 AccessDecisionVoter
。
若要呼叫現有的 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
,您可以執行
-
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
用於 @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);
AccessDecisionManager
的 decide
方法會傳遞它需要的所有相關資訊,以便制定授權決策。特別是,傳遞安全 Object
可讓檢查實際安全物件調用中包含的那些引數。例如,假設安全物件是 MethodInvocation
。您可以查詢 MethodInvocation
以取得任何 Customer
引數,然後在 AccessDecisionManager
中實作某種安全性邏輯,以確保主體被允許對該客戶執行操作。如果拒絕存取權,則預期實作會拋出 AccessDeniedException
。
supports(ConfigAttribute)
方法由 AbstractSecurityInterceptor
在啟動時呼叫,以判斷 AccessDecisionManager
是否可以處理傳遞的 ConfigAttribute
。supports(Class)
方法由安全性攔截器實作呼叫,以確保組態的 AccessDecisionManager
支援安全性攔截器呈現的安全物件類型。
基於投票的 AccessDecisionManager 實作
雖然使用者可以實作自己的 AccessDecisionManager
來控制授權的所有方面,但 Spring Security 包含多個基於投票的 AccessDecisionManager
實作。投票決策管理員說明了相關的類別。
下圖顯示了 AccessDecisionManager
介面

透過使用此方法,會輪詢一系列 AccessDecisionVoter
實作以取得授權決策。然後,AccessDecisionManager
會根據其對投票的評估,決定是否拋出 AccessDeniedException
。
AccessDecisionVoter
介面有三種方法
int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs);
boolean supports(ConfigAttribute attribute);
boolean supports(Class clazz);
具體的實作會傳回 int
,可能的值反映在名為 ACCESS_ABSTAIN
、ACCESS_DENIED
和 ACCESS_GRANTED
的 AccessDecisionVoter
靜態欄位中。如果投票實作對授權決策沒有意見,則傳回 ACCESS_ABSTAIN
。如果它有意見,則必須傳回 ACCESS_DENIED
或 ACCESS_GRANTED
。
Spring Security 提供了三個具體的 AccessDecisionManager
實作來統計投票。ConsensusBased
實作根據非棄權投票的共識授予或拒絕存取權。提供屬性來控制在票數相等或所有投票都棄權時的行為。如果收到一個或多個 ACCESS_GRANTED
投票,則 AffirmativeBased
實作會授予存取權(換句話說,只要至少有一個授予投票,就會忽略拒絕投票)。與 ConsensusBased
實作類似,有一個參數可以控制在所有投票者都棄權時的行為。UnanimousBased
提供者預期一致的 ACCESS_GRANTED
投票才能授予存取權,忽略棄權。如果有任何 ACCESS_DENIED
投票,它會拒絕存取權。與其他實作類似,有一個參數可以控制在所有投票者都棄權時的行為。
您可以實作自訂 AccessDecisionManager
,以不同方式統計投票。例如,來自特定 AccessDecisionVoter
的投票可能會獲得額外的權重,而來自特定投票者的拒絕投票可能會產生否決效果。
RoleVoter
Spring Security 提供的最常用 AccessDecisionVoter
是 RoleVoter
,它將組態屬性視為角色名稱,並在使用者已被指派該角色時投票以授予存取權。
如果有任何 ConfigAttribute
以 ROLE_
前綴開頭,它就會投票。如果有一個 GrantedAuthority
傳回一個 String
表示法(來自 getAuthority()
方法),該表示法與一個或多個以 ROLE_
前綴開頭的 ConfigAttributes
完全相等,則它會投票以授予存取權。如果沒有以 ROLE_
開頭的任何 ConfigAttribute
完全符合,則 RoleVoter
會投票拒絕存取權。如果沒有 ConfigAttribute
以 ROLE_
開頭,則投票者會棄權。
AuthenticatedVoter
我們隱含地看到的另一個投票者是 AuthenticatedVoter
,它可用於區分匿名、完全驗證和「記住我」驗證的使用者。許多網站允許在「記住我」驗證下進行某些有限的存取,但要求使用者透過登入來確認其身分以取得完全存取權。
當我們使用 IS_AUTHENTICATED_ANONYMOUSLY
屬性來授予匿名存取權時,此屬性正在由 AuthenticatedVoter
處理。如需更多資訊,請參閱 AuthenticatedVoter
。
自訂投票者
您也可以實作自訂 AccessDecisionVoter
,並在其中放入您想要的任何存取控制邏輯。它可能特定於您的應用程式(與業務邏輯相關),或者它可能實作一些安全性管理邏輯。例如,在 Spring 網站上,您可以找到一篇部落格文章,其中說明如何使用投票者即時拒絕存取帳戶已暫停的使用者。

與 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
組態屬性來實現。