
文章目錄
1. 前言
歡迎閱讀 Spring Security 實戰干貨 系列文章 。對於受限的訪問資源,並不是對所有認證通過的用戶開放的。比如 A 用戶的角色是會計,那么他就可以訪問財務相關的資源。B 用戶是人事,那么他只能訪問人事相關的資源。我們在 一文中也對基於角色的訪問控制的相關概念進行了探討。在實際開發中我們如何對資源進行角色粒度的管控呢?今天我來告訴你 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_APP 和 ROLE_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 拒絕。默認的 prefix 為 defaultRolePrefix= ROLE_ 。
3.2 通過判斷用戶的 GrantedAuthority 來進行訪問控制
我們也可以通過 hasAuthority 和 hasAnyAuthority 來判定。 其實底層實現和 hasAnyRole 方法一樣,只不過 prefix 為 null 。也就是你寫入的 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.1 和 2.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 的一些探討
開放請求 其實通常情況下跟 匿名請求 有交叉。它們的主要區別在於: 當前的 Authentication 為 null 時 permitAll 是放行的,而 anonymous 需要 Authentication 為 AnonymousAuthenticationToken 。這里是比較難以理解的,下面是來自 Spring 文檔中的一些信息:
通常,采用“默認拒絕”的做法被認為是一種良好的安全做法,在該方法中,您明確指定允許的內容,並禁止其他所有內容。定義未經身份驗證的用戶可以訪問的內容的情況與此類似,尤其是對於Web應用程序。許多站點要求用戶必須通過身份驗證才能使用少數幾個URL(例如,主頁和登錄頁面)。在這種情況下,最簡單的是為這些特定的URL定義訪問配置屬性,而不是為每個受保護的資源定義訪問配置屬性。換句話說,有時很高興地說默認情況下需要ROLE_SOMETHING,並且只允許該規則的某些例外,例如應用程序的登錄,注銷和主頁。您還可以從過濾器鏈中完全忽略這些頁面,從而繞過訪問控制檢查,
這就是我們所說的匿名身份驗證。
使用 permitAll() 將配置授權,以便在該特定路徑上允許所有請求(來自匿名用戶和已登錄用戶),anonymous() 主要是指用戶的狀態(是否登錄)。基本上,直到用戶被“認證”為止,它就是“匿名用戶”。就像每個人都有“默認角色”一樣。
7. 總結
基於配置來解決基於角色的訪問控制是常用的方案之一。也是最容易入門的 **Spring Security ** 訪問控制技術。下一期我們將介紹基於方法的訪問控制。敬請關注 felord.cn。
關注公眾號:Felordcn獲取更多資訊
