Authorization
在確定了用戶將如何進行身份驗證之后,還需要配置應用程序的授權規則。
Spring Security 中的高級授權功能是其受歡迎的最有說服力的原因之一。無論您選擇如何進行身份驗證(無論是使用 Spring Security 提供的機制和提供者,還是與容器或其他非 Spring Security 身份驗證授權機構集成) ,授權服務都可以在您的應用程序中以一致和簡單的方式使用。
您應該考慮附加授權規則來請求 URI 和方法。在這兩種情況下,您都可以偵聽并響應每個授權檢查發布的授權事件。下面還有大量關于 Spring Security 授權如何工作的細節,以及在建立了基本模型之后,如何對其進行微調。
Authorization Architecture
本節描述應用于授權的 SpringSecurity 體系結構。
Authorities
身份驗證討論所有身份驗證實現如何存儲 GrantedAuthority 對象列表。這些代表授予主體的權限。。GrantedAuthority 對象由 AuthenticationManager 插入到 Authentication 對象中,然后在進行授權決策時由 AccessDecisionManager 實例讀取。
GrantedAuthority 接口只有一個方法:
String getAuthority();
AuthorizationManager 實例使用此方法來獲取 GrantedAuthority 的精確 String 表示形式。通過返回一個 String 形式的表示,GrantedAuthority 可以被大多數 AuthorizationManager 實現輕松地“讀取”。如果 GrantedAuthority 不能精確地表示為 String,那么 GrantedAuthority 被認為是“復雜的”,而 getAuthority ()必須返回 null。
一個復雜的 GrantedAuthority 的示例可能是一個實現,它存儲了適用于不同客戶賬戶號碼的操作列表和權限閾值。將這種復雜的 GrantedAuthority 表示為一個字符串會相當困難。因此,getAuthority() 方法應該返回 null。這表示任何 AuthorizationManager 需要支持特定的 GrantedAuthority 實現以理解其內容。
Spring Security 包括一個具體的 GrantedAuthority 實現:SimpleGrantedAuthority。這個實現允許任何用戶指定的字符串被轉換為 GrantedAuthority。安全架構中包含的所有 AuthenticationProvider 實例都使用 SimpleGrantedAuthority 來填充 Authentication 對象。
默認情況下,基于角色的授權規則包括 ROLE_ 作為前綴。這意味著如果有需要安全上下文具有“USER”角色的授權規則,Spring Security 默認會查找返回“ROLE_USER”的 GrantedAuthority#getAuthority。
可以使用 GrantedAuthorityDefauls.GrantedAuthorityDefault 存在來自定義前綴,以便用于基于角色的授權規則。
您可以通過公開 GrantedAuthorityDefault bean 來配置授權規則以使用不同的前綴,如下所示:
Custom MethodSecurityExpressionHandler
@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {return new GrantedAuthorityDefaults("MYPREFIX_");
}
使用靜態方法公開 GrantedAuthorityDefault,以確保 Spring 在初始化 Spring Security 的方法 Security@Configuration 類之前發布它
Invocation Handling
SpringSecurity 提供了攔截器來控制對安全對象(如方法調用或 Web 請求)的訪問。AuthorizationManager 實例對是否允許繼續進行調用作出預調用決策。此外,AuthorizationManager 實例在調用后決定是否可以返回給定的值。
The AuthorizationManager
AuthorizationManager 取代 AccessDecisionManager 和 AccessDecisionVoter。
鼓勵自定義 AccessDecisionManager 或 AccessDecisionVoter 的應用程序更改為使用 AuthorizationManager。
AuthorizationManager 由 Spring Security 的基于請求、基于方法和基于消息的授權組件調用,并負責制定最終的訪問控制決策。AuthorizationManager 接口包含兩個方法:
AuthorizationDecision check(Supplier<Authentication> authentication, Object secureObject);default AuthorizationDecision verify(Supplier<Authentication> authentication, Object secureObject)throws AccessDeniedException {// ...
}
AuthorizationManager 的 check 方法被傳遞所有它需要的信息,以便做出授權決策。特別是,傳遞安全 Object 允許檢查實際安全對象調用中的所有參數。例如,假設安全對象是一個 MethodInvocation。很容易查詢 MethodInvocation 是否有任何 Customer 參數,然后實現一些安全邏輯在 AuthorizationManager 中,以確保主體被允許對該客戶進行操作。實現被期望返回一個正值的 AuthorizationDecision,如果訪問被授權;負值的 AuthorizationDecision,如果訪問被拒絕;以及一個 null AuthorizationDecision,當不作出決定時。
verify 方法調用 check 方法,并在遇到負值的 AuthorizationDecision 時,隨后拋出一個 AccessDeniedException。
Delegate-based AuthorizationManager Implementations
雖然用戶可以實現自己的 AuthorizationManager 來控制授權的所有方面,但 Spring Security 提供了一個授權 AuthorizationManager,它可以與各個 AuthorizationManager 協作。
RequestMatchergeneratingAuthorizationManager 將使請求與最合適的委托 AuthorizationManager 匹配。
Authorization Manager Implementations 說明了相關的類。
使用這種方法,可以對授權決策輪詢 AuthorizationManager 實現的組合。
AuthorityAuthorizationManager
Spring Security提供的最常見的 AuthorizationManager 是 AuthorityAuthorizationManager。它配置了一組給定的權限來查找當前的Authentication
。如果身份驗證包含任何配置的權限,它將返回正的 AuthorizationDecision。否則它將返回一個否定的授權決策。
AuthenticatedAuthorizationManager
另一個管理器是 AuthenticatedAuthorizationManager。它可以用來區分匿名、完全認證和記住我認證的用戶。許多網站允許在記住我認證下進行某些有限的訪問,但要求用戶通過登錄來確認他們的身份,以便獲得完全訪問權限。
AuthorizationManagers
AuthorizationManager 中還有一些有用的靜態工廠,可以將各個 AuthorizationManager 組合成更復雜的表達式。
Custom Authorization Managers
顯然,您也可以實現一個自定義的 AuthorizationManager,并且可以在其中放入您想要的幾乎任何訪問控制邏輯。它可能與您的應用程序(業務邏輯相關)有關,也可能實現一些安全管理邏輯。例如,您可以創建一個實現,它可以查詢 Open Policy Agent 或您自己的授權數據庫。
您將在 Spring 網站上找到一篇 blog article ,其中描述了如何使用遺留的 AccessDecisionVoter 來拒絕實時訪問那些帳戶已被暫停的用戶。您可以通過實現 AuthorizationManager 來實現相同的結果。
Adapting AccessDecisionManager and AccessDecisionVoters
在 AuthorizationManager 之前,Spring Security 發布了 AccessDecisionManager 和 AccessDecisionVoter。
在某些情況下,比如遷移較舊的應用程序,可能需要引入一個 AuthorizationManager 來調用 AccessDecisionManager 或 AccessDecisionVoter。
要調用現有的 AccessDecisionManager,可以執行以下操作:
Adapting an AccessDecisionManager
@Component
public class AccessDecisionManagerAuthorizationManagerAdapter implements AuthorizationManager {private final AccessDecisionManager accessDecisionManager;private final SecurityMetadataSource securityMetadataSource;@Overridepublic 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);}}@Overridepublic void verify(Supplier<Authentication> authentication, Object object) {Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);this.accessDecisionManager.decide(authentication.get(), object, attributes);}
}
然后連接到你的安全過濾器鏈。
或者只調用 AccessDecisionVoter,你可以這樣做:
Adapting an AccessDecisionVoter
@Component
public class AccessDecisionVoterAuthorizationManagerAdapter implements AuthorizationManager {private final AccessDecisionVoter accessDecisionVoter;private final SecurityMetadataSource securityMetadataSource;@Overridepublic 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;}
}
然后連接到你的安全過濾器鏈。
Hierarchical Roles
應用程序中的特定角色應該自動“包含”其他角色,這是一個常見的要求。例如,在具有“管理員”和“用戶”角色概念的應用程序中,您可能希望管理員能夠做普通用戶能夠做的所有事情。要實現這一點,您可以確保所有管理用戶也被分配了“用戶”角色。或者,您可以修改每個需要“user”角色的訪問約束,使其也包含“admin”角色。如果您的應用程序中有許多不同的角色,這可能會變得相當復雜。
角色層次結構的使用允許您配置哪些角色(或權限)應該包括其他角色。
這支持基于過濾器的授權在 HttpSecurity#authorizeHttpRequests 中,以及通過 DefaultMethodSecurityExpressionHandler 進行基于方法的安全授權,SecuredAuthorizationManager 用于 @Secured,Jsr250AuthorizationManager 用于 JSR-250 注解。您可以以以下方式一次性配置它們的行為:
@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;
}
在這里,我們有四個角色的層次結構 ROLE_ADMIN ? ROLE_STAFF ? ROLE_USER ? ROLE_GUEST。一個具有 ROLE_ADMIN 認證的用戶,在評估任何基于過濾器或方法的安全約束時,將表現得好像他們擁有所有四個角色。
角色層次結構為您的應用程序提供了簡化訪問控制配置數據的便捷方式,以及減少您需要分配給用戶的角色權限的數量。對于更復雜的需求,您可能希望定義一個邏輯映射,將您的應用程序所需的具體訪問權限與分配給用戶的角色之間進行轉換,并在加載用戶信息時進行轉換。
Legacy Authorization Components
SpringSecurity 包含一些遺留組件。因為它們還沒有被刪除,所以出于歷史目的包含了文檔。他們推薦的替代品在上面。
The 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 支持安全攔截器呈現的安全 Object 的類型。
Voting-Based AccessDecisionManager Implementations
雖然用戶可以實現自己的 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);
具體實現返回一個整數,可能的值反映在 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 _ 前綴開頭的 ConfigAttritribute 完全相等,它將投票授予訪問權。如果沒有與以 ROLE _ 開頭的任何 ConfigAttribute 完全匹配,RoleVoter 將投票拒絕訪問。如果沒有以 ROLE _ 開頭的 ConfigAttribute,則選民棄權。
AuthenticatedVoter
我們隱式看到的另一個選民是 AuthenticatedVoter,它可以用來區分匿名、完全身份驗證和 remember-me 身份驗證用戶。許多站點允許在 remember-me 身份驗證下進行某些有限的訪問,但是需要用戶通過登錄進行完全訪問來確認他們的身份。
當我們使用 IS _ AUTHENTICATION _ ANONYMOUSLY 屬性授予匿名訪問權限時,AuthenticatedVoter 正在處理該屬性。有關更多信息,請參見 AuthenticatedVoter。
Custom Voters
您還可以實現一個自定義 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 屬性來實現。