小米安全Dayeh:《Spring Security入坑指南1》
Spring Security 01 — Basic Introduction Part I
Spring Security 5.1.4 RELEASE
對於使用Java進行開發的同學來說,Spring算是一個比較熱門的選擇,包括現在流行的Spring Boot更是能快速上手,並讓開發者更好的關注業務核心的開發,而免去了原來冗雜的配置過程。從Spring 4開始推薦使用程式碼進行配置,更是降低了配置的難度,遠離了讓人看得頭大的xml配置檔案。
這篇文章主要想系統的介紹一下Spring Security這個框架。當需要進行一些認證授權的開發時,常用的Java安全框架主要有Apache Shiro和Spring Security。兩者相比,Shiro更為輕量化,簡單易用,而Spring Security作為Spring的親兒子,功能更強大,和Spring專案的結合度更好,而使用的學習成本相較於Shiro會略高一些。
在講程式碼之前,想先介紹一下Spring Security中的一些基本元件及服務,以便於更好理解後文的程式碼。基本架構的介紹,主要來自於官方文件,進行了選擇性的翻譯,參考的版本是Spring Security 5.1.4 RELEASE,感興趣的同學也可以直接前往閱讀英文原文。
1 基本架構
1.1 核心元件
從Spring Security 3.0開始,元件spring-security-corejar包中的內容進行了精簡,不再包含任何web應用安全,LDAP或名稱空間配置的程式碼。
SecurityContextHolder
最基本的物件,用來儲存當前的安全上下文(security context),包含了當前登入的使用者資訊。預設使用ThreadLocal儲存細節資訊,因此這些資訊對於同一個執行緒內容呼叫的方法都是可用的。當使用者的請求處理完成後,框架會自動清理執行緒而不必使用者關心。但是由於某些應用由於其對執行緒的使用方式,並不適合使用ThreadLocal,則需要根據情況,在啟動前設定SecurityContextHolder的策略。
在SecurityContextHolder中儲存了當前活躍使用者的資訊。Spring Security使用一個Authentication物件來表示這些資訊。在程式的任何地方,都可以用以下程式碼來獲取當前認證使用者的資訊。
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (principal instanceof UserDetails) { String username = ((UserDetails)principal).getUsername(); } else { String username = principal.toString(); }
UserDetailsService
在介面UserDetailService中只有一個方法,接收一個字串返回一個UserDetails物件,當認證成功後,UserDetails會被用來構造一個Authentication物件儲存在SecurityContextHolder中。
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
UserDetailsService的實現,主要是用來載入使用者資訊,並向其他元件提供這些資訊,並不能進行認證使用者的操作,認證的操作由AuthenticationManager完成。
如果使用者想自定義一個認證流程,則需要實現AuthenticationProvider介面。
GrantedAuthority
Authentication的getAuthorities( )方法返回GrantedAuthority的物件陣列。GrantAuthority物件一般由UserDetailsService載入。
總結
ScurityContextHolder獲取SecurityContext
SecurityContext儲存了Authentication以及其他一些請求相關的安全資訊
Authentication表示一個認證使用者資訊
GrantedAuthority表示授予使用者的許可權資訊
UserDetails包含了構建Authentication物件需要的必要資訊,這些資訊來自於應用的DAO或其他資料來源
UserDetailsService根據傳入使用者名稱字串構建一個UserDetails物件
1.2 認證環節
一個基本的認證環節包括:
- 使用者輸入使用者名稱和密碼
- 系統驗證使用者名稱密碼正確
- 系統獲取該使用者的角色、許可權等資訊。
以上三個步驟完成了一個認證過程,在Spring Security中,相應地完成了以下動作:
- 後端獲取到使用者名稱和密碼並用之生成一個UsernamePasswordAuthenticationToken物件,UsernamePasswordAuthenticationToken是Authentication的一個實現類。
- token被傳入AuthenticationManager的例項中進行校驗
- 認證成功後,AuthenticationManager會返回一個Authentication例項,其中包括了使用者所有細節資訊,包括角色、許可權等
- 透過呼叫SecurityContextHolder.getContext().setAuthentication(…)建立安全上下文,傳入Authentication物件
完成以上過程後,當前使用者認證完成。以下程式碼示範了一個認證環節最基本的流程(並非SpringSecurity框架原始碼)
import org.springframework.security.authentication.*; import org.springframework.security.core.*; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; public class AuthenticationExample { // 0. 建立一個AuthenticationManager例項,之後用於使用者校驗 (具體實現在下方) private static AuthenticationManager am = new SampleAuthenticationManager(); public static void main(String[] args) throws Exception { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); // 1. 使用者在介面輸入使用者名稱密碼 while(true) { System.out.println("Please enter your username:"); String name = in.readLine(); System.out.println("Please enter your password:"); String password = in.readLine(); try { // 2. 使用者名稱和密碼生成一個UsernamePasswordAuthenticationToken物件 Authentication request = new UsernamePasswordAuthenticationToken(name, password); // 3. 使用AuthenticationManager例項校驗token Authentication result = am.authenticate(request); // 4. 校驗成功,將包含使用者資訊的Authentication例項加入security context SecurityContextHolder.getContext().setAuthentication(result); break; } catch(AuthenticationException e) { // 認證失敗,捕獲異常 System.out.println("Authentication failed: " + e.getMessage()); } } System.out.println("Successfully authenticated. Security context contains: " + SecurityContextHolder.getContext().getAuthentication()); } } // AuthenticationManager的實現 class SampleAuthenticationManager implements AuthenticationManager { static final List<GrantedAuthority> AUTHORITIES = new ArrayList<GrantedAuthority>(); static { AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER")); } public Authentication authenticate(Authentication auth) throws AuthenticationException { //該方法內寫認證透過的條件,此處demo判斷條件是,使用者名稱等於密碼即認證透過 if (auth.getName().equals(auth.getCredentials())) { return new UsernamePasswordAuthenticationToken(auth.getName(), auth.getCredentials(), AUTHORITIES); } throw new BadCredentialsException("Bad Credentials"); } }
直接設定SecurityContextHolder
Spring Security並不關心一個Authentication例項是如何放進SecurityContextHolder的,只要保證在SecurityContextHolder中有一個有效的Authentication表示一個認證的使用者即可,然後AbstractSecurityInterceptor便可用來授權使用者的操作了。因此,開發者亦可選擇使用其他認證框架提供的認證資訊。開發者只需要寫使用一個過濾器獲取來自第三方的使用者資訊,然後構造一個Authentication物件放入到SecurityContextHolder即可。但是如果不使用內建的認證,有些原本自動完成的事情就需要由開發者來處理了,比如需要在一開始建立HTTP session來快取上下文。
1.3 Web應用中的認證
在處理Web應用中的認證,Spring Security中主要參與的有ExceptionTranslationFilter和AuthenticationEntryPoint,以及一個認證機制,用來呼叫AuthenticationManager完成核心的認證部分。
ExceptionTranslationFilter
是一個Spring Security的過濾器,用來檢測所有丟擲的Spring Security的異常,通常這些異常都由AbstractSecurityInterceptor丟擲。AbstractSecurityInterceptor只負責丟擲異常,而ExceptionTranslationFilter則負責確定如何處理異常,比如當前使用者認證了但許可權不夠,則返回403錯誤碼,或者當前使用者還未認證,則發起一個AuthenticationPoint。
Authentication Mechanism
當瀏覽器提交使用者的認證資訊後,伺服器需要收集這些資訊,而在Spring Security中,從客戶端獲取認證資訊的功能稱為“認證機制” (authentication mechanism)。比如Basic authentication,當收集到客戶端提交的認證資訊後,後端就會建立一個Authentication物件,然後交給AuthenticationManager校驗。
隨後authentication mechanism會收到一個包含完整資訊的Authentication物件,並認為請求合法,然後把Authentication放入SecurityContextHolder,隨後原請求便會發起重試。如果AuthenticationManager拒絕了請求,那麼認證機制便會要求客戶端重試。
儲存使用者認證資訊
一般在一個Web應用中,使用者登入後,伺服器會快取使用者的認證資訊,使用者後續的操作透過其session id進行身份認證。Spring Security框架中,儲存SecurityContext的任務交給SecurityContextPersistenceFilter,其預設將安全上下文資訊儲存為HttpSessio屬性。每當請求來,它都會透過SecurityContextHolder來獲取認證資訊,並在請求結束後,清除SecurityContextHolder。出於安全考慮,不要直接從HttpSession中去獲取安全上下文,而應該透過SecurityContextHolder獲取。
1.4 許可權控制(授權)
Spring Security的許可權控制,依賴於AOP。許可權控制可以應用在方法呼叫上,也可用在web請求上。Spring Security中主要負責進行許可權控制決定的是AccessDecisionManager。
Secure Objects
安全物件指一切可以加上安全配置的物件,最常見的例子是方法的呼叫和web請求。
每個支援的安全物件都有一個自己的攔截器,這個攔截器是AbstractSecurityInterceptor子類,當AbstractSecurityInterceptor被呼叫的時候,SecurityContextHolder中會包含一個有效的Authentication物件,如果當前使用者主體已經被認證。AbstractSecurityInterceptor在處理安全物件的請求時候,流程如下:
- 檢視和當前請求關聯的配置屬性(configuration attributes)
- 將當前的安全物件、Authentication物件以及配置屬性提交給AccessDecisionManager,由其做一個授權的決定。
- 在呼叫發生的位置可選的更換Authentication物件
- 完成授權後,允許安全物件的呼叫進行。
- 當呼叫返回後,即刻呼叫AfterInvocationManager(如果配置了)
Configuration Attributes
配置屬性用介面ConfigAttribute表示,可以理解為被AbstractSecurityInterceptor使用的具有特殊意義的字串。AbsractSecurityInterceptor中配置了SecurityMetadataSource用來檢視安全物件的配置屬性。配置屬性可以用來簡單表示一個角色名,或者更復雜的意義,它的用處取決於AccessDecisionManager實現的複雜性。或者簡單來說,配置屬性只是表示特殊含義的,比如角色名的字串,但是其具體如何解讀,取決於AccessDedcisionManager的實現。舉個簡單得例子,當我們使用預設的AccessDedcisionManager的實現時,可以在一個方法或者一個url請求的註釋里加入配置屬性ROLE_A,這表示,只有當使用者的GrantedAuthority匹配ROLE_A的時候,才被允許使用這個方法或呼叫這個請求。這裡只做簡單得說明,具體使用在後文中展開。
Security interceptors and the "secure object" model
下圖是安全攔截器和安全物件的模型,給出了各個元件之間的關係,有個別元件在簡介中沒有提到,將在後文的使用說明中展開。
相關文章
- 小米安全Dayeh:《Spring Security入坑指南2》2019-03-15Spring
- Spring Security安全綜合大全指南2024-04-15Spring
- 【Spring Security】1.快速入門2020-08-10Spring
- Spring Security(二)登入與安全控制2018-09-22Spring
- Spring Security入門(3-1)Spring Security的登入頁面定製2017-06-13Spring
- Spring Security 安全框架2015-06-29Spring框架
- Java安全框架(一)Spring Security2020-11-03Java框架Spring
- vim 入坑指南2018-03-20
- GreenDao入坑指南2017-12-14
- Spring Security 快速入門2019-03-02Spring
- Spring Security 入門篇2021-05-10Spring
- Spring Security(一)入門2018-09-15Spring
- UIStackView 入坑指南2019-01-04UIView
- Spring Security3.0入門2014-03-11Spring
- Spring Security原始碼分析五:Spring Security實現簡訊登入2018-01-15Spring原始碼
- uni-app 入坑指南2019-02-22APP
- Oracle函式入坑指南2022-12-22Oracle函式
- rust入坑指南之ownership2023-02-22Rust
- React+Redux入坑指南2016-08-01ReactRedux
- Spring Boot 整合 Spring Security 入門案例教程2020-04-21Spring Boot
- Activiti7 與 Spring Boot 及 Spring Security 整合 踩坑記錄2021-07-14Spring Boot
- Spring Security 之 rememberMe 自動登入2020-06-26SpringREM
- Spring Security 入門原理及實戰2019-05-16Spring
- Spring Security使用(二) 非同步登入2020-11-29Spring非同步
- spring security 自定義認證登入2017-12-21Spring
- Spring入門指南2024-07-26Spring
- Omi 入坑指南 Third field 事件入門2018-11-29事件
- Go Web開發入坑指南2019-03-26GoWeb
- CTF萌新入坑指南(web篇)2020-09-27Web
- Spring Security2022-05-01Spring
- Flutter beta3 避坑指南12018-05-24Flutter
- [譯] 學習 Spring Security(八):使用 Spring Security OAuth2 實現單點登入2018-04-08SpringOAuth
- 手把手帶你入門 Spring Security!2019-07-25Spring
- Spring Security系列之入門應用(二)2018-12-10Spring
- Spring Security OAuth2 單點登入2021-11-08SpringOAuth
- spring security 6.0.8(boot 3.0.13)自定義 filter 踩坑-已解決2024-04-03SpringbootFilter
- Spring Security原始碼分析八:Spring Security 退出2019-03-02Spring原始碼
- Spring Cloud Gateway入坑記2019-05-04SpringCloudGateway