Spring Security 實戰乾貨:基於配置的介面角色訪問控制

碼農小胖哥發表於2019-11-14

1. 前言

歡迎閱讀 Spring Security 實戰乾貨 系列文章 。對於受限的訪問資源,並不是對所有認證通過的使用者開放的。比如 A 使用者的角色是會計,那麼他就可以訪問財務相關的資源。B 使用者是人事,那麼他只能訪問人事相關的資源。我們在RBAC概念 一文中也對基於角色的訪問控制的相關概念進行了探討。在實際開發中我們如何對資源進行角色粒度的管控呢?今天我來告訴你 Spring Security 是如何來解決這個問題的。

2. 將角色寫入 UserDetails

我們使用 UserDetailsService 載入 UserDetails 時也會把使用者的 GrantedAuthority 許可權集寫入其中。你可以將角色持久化並在這個點進行注入然後配置訪問策略,後續的問題交給 Spring Security

3. 在 HttpSecurity 中進行配置角色訪問控制

我們可以通過配置 WebSecurityConfigurerAdapter 中的 HttpSecurity 來控制介面的角色訪問。

3.1 通過判斷使用者是否持有角色來進行訪問控制

httpSecurity.authorizeRequests().antMatchers("/foo/test").hasRole("ADMIN")

表示 持有 ROLE_ADMIN 角色的使用者才能訪問 /foo/test 介面。注意:hasRole(String role) 方法入參不能攜帶字首 ROLE_ 。我們來檢視 SecurityExpressionRoot 中相關原始碼:


	public final boolean hasRole(String role) {
		return hasAnyRole(role);
	}

複製程式碼

很明顯 hasRole 方法源於 hasAnyRole (持有任何其中角色之一,就能滿足訪問條件,用於一個介面開放給多個角色訪問時) :

 
	public final boolean hasAnyRole(String... roles) {
		return hasAnyAuthorityName(defaultRolePrefix, roles);
	}
複製程式碼

如果一個介面開放給多個角色,比如 /foo/test 開放給了 ROLE_APPROLE_ADMIN 可以這麼寫:

httpSecurity.authorizeRequests().antMatchers("/foo/test").hasAnyRole("APP","ADMIN")

hasAnyRole 方法最終的實現為 hasAnyAuthorityName(String prefix, String... roles):

private boolean hasAnyAuthorityName(String prefix, String... roles) {
		Set<String> roleSet = getAuthoritySet();

		for (String role : roles) {
			String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
			if (roleSet.contains(defaultedRole)) {
				return true;
			}
		}

		return false;
	}
複製程式碼

上面才是根本的實現, 需要一個 prefix 和每一個 role 進行拼接,然後使用者的角色集合 roleSet 中包含了就返回true 放行,否則就 false 拒絕。預設的 prefixdefaultRolePrefix= ROLE_

3.2 通過判斷使用者的 GrantedAuthority 來進行訪問控制

我們也可以通過 hasAuthorityhasAnyAuthority 來判定。 其實底層實現和 hasAnyRole 方法一樣,只不過 prefixnull 。也就是你寫入的 GrantedAuthority 是什麼樣子的,這裡傳入引數的就是什麼樣子的,不再受 ROLE_ 字首的制約。

2.1 章節的寫法等同如下的寫法:

httpSecurity.authorizeRequests().antMatchers("/foo/test").hasAuthority("ROLE_ADMIN")

httpSecurity.authorizeRequests().antMatchers("/foo/test").hasAnyAuthority("ROLE_APP","ROLE_ADMIN")

4. 匿名訪問

匿名身份驗證的使用者和未經身份驗證的使用者之間沒有真正的概念差異。Spring Security 的匿名身份驗證只是為您提供了一種更方便的方式來配置訪問控制屬性。所有的匿名使用者都持有角色 ROLE_ANONYMOUS 。所以你可以使用 2.12.2 章節的方法來配置匿名訪問:

httpSecurity.authorizeRequests().antMatchers("/foo/test").hasAuthority("ROLE_ANONYMOUS")

你也可以通過以下方式進行配置:

httpSecurity.authorizeRequests().antMatchers("/foo/test").anonymous()

5. 開放請求

開放請求可以這麼配置:

httpSecurity.authorizeRequests().antMatchers("/foo/test").permitAll()

6. permitAll 與 anonymous 的一些探討

開放請求 其實通常情況下跟 匿名請求 有交叉。它們的主要區別在於: 當前的 AuthenticationnullpermitAll 是放行的,而 anonymous 需要 AuthenticationAnonymousAuthenticationToken 。這裡是比較難以理解的,下面是來自 Spring 文件中的一些資訊:

通常,採用“預設拒絕”的做法被認為是一種良好的安全做法,在該方法中,您明確指定允許的內容,並禁止其他所有內容。定義未經身份驗證的使用者可以訪問的內容的情況與此類似,尤其是對於Web應用程式。許多站點要求使用者必須通過身份驗證才能使用少數幾個URL(例如,主頁和登入頁面)。在這種情況下,最簡單的是為這些特定的URL定義訪問配置屬性,而不是為每個受保護的資源定義訪問配置屬性。換句話說,有時很高興地說預設情況下需要ROLE_SOMETHING,並且只允許該規則的某些例外,例如應用程式的登入,登出和主頁。您還可以從過濾器鏈中完全忽略這些頁面,從而繞過訪問控制檢查, 這就是我們所說的匿名身份驗證。

使用 permitAll() 將配置授權,以便在該特定路徑上允許所有請求(來自匿名使用者和已登入使用者),anonymous() 主要是指使用者的狀態(是否登入)。基本上,直到使用者被“認證”為止,它就是“匿名使用者”。就像每個人都有“預設角色”一樣。

7. 總結

基於配置來解決基於角色的訪問控制是常用的方案之一。也是最容易入門的 **Spring Security ** 訪問控制技術。下一期我們將介紹基於方法的訪問控制。敬請關注 felord.cn。

關注公眾號:Felordcn獲取更多資訊

個人部落格:https://felord.cn

Spring Security 實戰乾貨:基於配置的介面角色訪問控制

相關文章