Spring Security是Spring 框架的擴充套件,可以輕鬆地將常見的安全實踐構建到我們的應用程式中。這包括使用者身份驗證和授權、API 保護等等。
在本教程中,我們將瞭解 Spring Security 內部的眾多部分之一:AuthorizationManager。我們將瞭解它如何融入更大的 Spring Security 生態系統,以及它如何幫助保護我們的應用程式的各種用例。
什麼是Spring Security AuthorizationManager
Spring AuthorizationManager是一個介面,允許我們檢查經過身份驗證的實體是否有權訪問安全資源。 Spring Security 使用AuthorizationManager例項為基於請求、基於方法和基於訊息的元件做出最終訪問控制決策。
作為背景,Spring Security 有幾個關鍵概念,在瞭解AuthorizationManager的具體角色之前有助於理解這些概念 :
- 實體Entity:任何可以向系統發出請求的東西。例如,這可能是人類使用者或遠端 Web 服務。
- 身份驗證Authentication:驗證實體是否是其自稱身份的過程。這可以透過使用者名稱/密碼、令牌或任意數量的其他方法來實現。
- 授權Authorization:驗證實體是否有權訪問資源的過程
- 資源Resource:系統可供訪問的任何資訊,例如 URL 或文件
- 許可權Authority:通常稱為角色,這是表示實體擁有的許可權的邏輯名稱。單個實體可能被授予零個或多個許可權。
考慮到這些概念,我們可以更深入地研究AuthorizationManager介面。
如何使用AuthorizationManager
AuthorizationManager是一個簡單的介面,僅包含兩個方法:
- AuthorizationDecision check(Supplier<Authentication> authentication, T object);
- void verify(Supplier<Authentication> authentication, T object);
這兩種方法看起來很相似,因為它們採用相同的引數:
- authentication身份驗證:提供代表發出請求的實體的身份驗證物件的供應商。
- object:被請求的安全物件(根據請求的性質而變化)
然而,每種方法都有不同的目的。
- 第一個方法返回一個AuthorizationDecision,它是一個布林值的簡單包裝,指示實體是否可以訪問安全物件。
- 第二種方法不返回任何內容。相反,它只是執行授權檢查,如果實體無權訪問安全物件,則丟擲AccessDeniedException。
AuthorizationManager介面是在Spring Security 5.0中引入的。在此介面之前,授權的主要方法是透過AccessDecisionManager介面。雖然AccessDecisionManager介面在 Spring Security 的最新版本中仍然存在,但它已被棄用,應該避免使用AuthorizationManager。
AuthorizationManager的實現
Spring 提供了多種 AuthorizationManager介面的實現。在下面的部分中,我們將瞭解其中的幾個。
1. AuthenticatedAuthorizationManager
我們要檢視的第一個實現是AuthenticatedAuthorizationManager。簡而言之,此類僅根據實體是否經過身份驗證返回肯定的授權決策。此外,它還支援三個級別的身份驗證:
- 匿名:實體未經身份驗證
- 記住我:該實體已透過身份驗證並且正在使用記住的憑據
- 完全經過身份驗證:實體經過身份驗證並且不使用記住的憑據
請注意,這是Spring Boot為基於 Web 的應用程式建立的預設AuthorizationManager 。預設情況下,所有端點都將允許訪問,無論角色或許可權如何,只要它來自經過身份驗證的實體即可。
2. AuthoritiesAuthorizationManager
該實現的工作原理與前一個類似,但它可以根據多個授權做出決定。它更適用於需要由多個授權訪問資源的複雜應用程式。
考慮一個使用不同角色管理釋出流程的部落格系統。作者和編輯角色都可以訪問用於建立和儲存文章的資源。但是,只有編輯角色才能訪問用於釋出的資源。
3.AuthorityAuthorizationManager
這種實現方式相當簡單。它根據實體是否具有特定角色來做出所有授權決定。
對於每個資源只需要一個角色或許可權的簡單應用,這種實現方式非常有效。例如,它可以很好地保護特定的 URL 集,使其只對具有管理員角色的實體開放。
請注意,該實現將決策權委託給 AuthoritiesAuthorizationManager 的一個例項。這也是 Spring 在自定義 SecurityFilterChain 時呼叫 hasRole() 或 hasAuthorities() 時使用的實現。
4. RequestMatcherDelegatingAuthorizationManager
該實現實際上並不做出授權決定。相反,它會根據 URL 模式委託給另一個實現,通常是上述管理器類中的一個。
例如,如果我們有一些 URL 是公開的,任何人都可以訪問,那麼我們可以將這些 URL 委託給一個總是返回肯定授權的無操作實現。然後,我們可以將安全請求委託給 AuthoritiesAuthorizationManager,由它來處理角色檢查。
事實上,這正是 Spring 在我們向 SecurityFilterChain 新增新請求匹配器時所做的工作。每次我們配置新的請求匹配器並指定一個或多個所需的角色或授權時,Spring 就會建立該類的新例項以及相應的委託。
5. ObservationAuthorizationManager
我們要了解的最後一個實現是 ObservationAuthorizationManager。該類實際上只是另一種實現的封裝,並增加了記錄與授權決策相關的指標的功能。只要應用程式中存在有效的 ObservationRegistry,Spring 就會自動使用該實現。
6. 其他實現
值得一提的是,Spring Security 中還有其他一些實現。它們大多與用於確保方法安全的各種 Spring 安全註解有關:
- SecuredAuthorizationManager -> @Secured
- PreAuthorizeAuthorizationManager -> @PreAuthorize
- PostAuthorizeAuthorizationManager -> @PostAuthorize
從本質上講,我們可以用來確保資源安全的任何 Spring 安全註解都有一個相應的 AuthorityManager 實現。
使用多個AuthorizationManager
在實踐中,我們很少只使用AuthorizationManager的單個例項。讓我們看一下SecurityFilterChain bean 的示例:
@Bean |
此示例使用五個不同的AuthorizationManager例項:
- 對hasRole() 的呼叫建立了一個AuthorityAuthorizationManager例項,該例項又委託給一個新的AuthoritiesAuthorizationManager例項。
- 對hasAnyRole()的呼叫還會建立一個AuthorityAuthorizationManager例項,該例項又委託給一個新的AuthoritiesAuthorizationManager例項。
- 對PermitAll()的呼叫使用 Spring Security 提供的靜態無操作AuthorizationManager,它始終提供積極的授權決策。
具有自己角色的附加請求匹配器以及任何基於方法的註釋都將建立附加的AuthorizationManager例項。
使用自定義AuthorizationManager
上面提供的實現足以滿足許多應用程式的需要。然而,與 Spring 中的許多介面一樣,完全可以建立一個自定義的AuthorizationManager來滿足我們的任何需求。
讓我們定義一個自定義的AuthorizationManager:
AuthorizationManager<RequestAuthorizationContext> customAuthManager() { |
然後,我們在自定義SecurityFilterChain時傳遞此例項:
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { |
在本例中,我們使用RequestAuthorizationContext做出授權決策。此類提供對底層 HTTP 請求的訪問,這意味著我們可以根據 cookie、標頭等內容做出決策。我們還可以委託第三方服務、資料庫或快取等結構來做出我們想要的任何型別的授權決策。
結論
在本文中,我們仔細研究了 Spring Security 如何處理授權。我們看到了通用的AuthorizationManager介面以及它的兩個方法如何做出授權決策。
我們還看到了該實現的各種實現,以及它們如何在 Spring Security 框架的各個地方使用。
最後,我們建立了一個簡單的自定義實現,可用於在應用程式中做出我們需要的任何型別的授權決策。
可以在 GitHub 上獲取。