SpringSecurity認證流程詳解
SpringSecurity基本原理
在之前的文章《SpringBoot + Spring Security 基本使用及個性化登錄配置》中對SpringSecurity進行了簡單的使用介紹,基本上都是對于接口的介紹以及功能的實現(xiàn)。 這一篇文章嘗試從源碼的角度來上對用戶認證流程做一個簡單的分析。
在具體分析之前,我們可以先看看SpringSecurity的大概原理:
SpringSecurity基本原理
其實比較簡單,主要是通過一系列的Filter對請求進行攔截處理。
認證處理流程說明
我們直接來看UsernamePasswordAuthenticationFilter
類,
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter // 登錄請求認證 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { // 判斷是否是POST請求 if (this.postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } else { // 獲取用戶,密碼 String username = this.obtainUsername(request); String password = this.obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); // 生成Token, UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); this.setDetails(request, authRequest); // 進一步驗證 return this.getAuthenticationManager().authenticate(authRequest); } } }
在attemptAuthentication
方法中,主要是進行username和password請求值的獲取,然后再生成一個UsernamePasswordAuthenticationToken 對象,進行進一步的驗證。
不過我們可以先看看UsernamePasswordAuthenticationToken 的構(gòu)造方法
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) { // 設(shè)置空的權(quán)限 super((Collection)null); this.principal = principal; this.credentials = credentials; // 設(shè)置是否通過了校驗 this.setAuthenticated(false); }
其實UsernamePasswordAuthenticationToken是繼承于Authentication
,該對象在上一篇文章中有提到過,它是處理登錄成功回調(diào)方法中的一個參數(shù),里面包含了用戶信息、請求信息等參數(shù)。
所以接下來我們看
this.getAuthenticationManager().authenticate(authRequest);
這里有一個AuthenticationManager,但是真正調(diào)用的是ProviderManager
。
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean { public Authentication authenticate(Authentication authentication) throws AuthenticationException { Class<? extends Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; Authentication result = null; boolean debug = logger.isDebugEnabled(); Iterator var6 = this.getProviders().iterator(); while(var6.hasNext()) { AuthenticationProvider provider = (AuthenticationProvider)var6.next(); // 1.判斷是否有provider支持該Authentication if (provider.supports(toTest)) { // 2. 真正的邏輯判斷 result = provider.authenticate(authentication); } } }
- 這里首先通過provider判斷是否支持當前傳入進來的Authentication,目前我們使用的是UsernamePasswordAuthenticationToken,因為除了帳號密碼登錄的方式,還會有其他的方式,比如SocialAuthenticationToken。
- 根據(jù)我們目前所使用的UsernamePasswordAuthenticationToken,provider對應(yīng)的是DaoAuthenticationProvider。
public Authentication authenticate(Authentication authentication) throws AuthenticationException { UserDetails user = this.userCache.getUserFromCache(username); if (user == null) { cacheWasUsed = false; // 1.去獲取UserDetails user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication); } try { // 2.用戶信息預(yù)檢查 this.preAuthenticationChecks.check(user); // 3.附加的檢查(密碼檢查) this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication); } catch (AuthenticationException var7) { } // 4.最后的檢查 this.postAuthenticationChecks.check(user); // 5.返回真正的經(jīng)過認證的Authentication return this.createSuccessAuthentication(principalToReturn, authentication, user); }
- 去調(diào)用自己實現(xiàn)的UserDetailsService,返回UserDetails
- 對UserDetails的信息進行校驗,主要是帳號是否被凍結(jié),是否過期等
- 對密碼進行檢查,這里調(diào)用了PasswordEncoder
- 檢查UserDetails是否可用。
- 返回經(jīng)過認證的Authentication
這里的兩次對UserDetails的檢查,主要就是通過它的四個返回boolean類型的方法。
經(jīng)過信息的校驗之后,通過UsernamePasswordAuthenticationToken
的構(gòu)造方法,返回了一個經(jīng)過認證的Authentication。
拿到經(jīng)過認證的Authentication之后,會再去調(diào)用successHandler。或者未通過認證,去調(diào)用failureHandler。
認證結(jié)果如何在多個請求之間共享
再完成了用戶認證處理流程之后,我們思考一下是如何在多個請求之間共享這個認證結(jié)果的呢?
因為沒有做關(guān)于這方面的配置,所以可以聯(lián)想到默認的方式應(yīng)該是在session中存入了認證結(jié)果。
那么是什么時候存放入session中的呢?
我們可以接著認證流程的源碼往后看,在通過attemptAuthentication方法后,如果認證成功,會調(diào)用successfulAuthentication,該方法中,不僅調(diào)用了successHandler,還有一行比較重要的代碼
SecurityContextHolder.getContext().setAuthentication(authResult);
SecurityContextHolder是對于ThreadLocal的封裝。 ThreadLocal是一個線程內(nèi)部的數(shù)據(jù)存儲類,通過它可以在指定的線程中存儲數(shù)據(jù),數(shù)據(jù)存儲以后,只有在指定線程中可以獲取到存儲的數(shù)據(jù),對于其他線程來說則無法獲取到數(shù)據(jù)。 更多的關(guān)于ThreadLocal的原理可以看看我以前的文章。
一般來說同一個接口的請求和返回,都會是在一個線程中完成的。我們在SecurityContextHolder中放入了authResult,再其他地方也可以取出來的。
最后就是在SecurityContextPersistenceFilter
中取出了authResult,并存入了session
SecurityContextPersistenceFilter也是一個過濾器,它處于整個Security過濾器鏈的最前方,也就是說開始驗證的時候是最先通過該過濾器,驗證完成之后是最后通過。
獲取認證用戶信息
/** * 獲取當前登錄的用戶 * @return 完整的Authentication */ @GetMapping("/me1") public Object currentUser() { return SecurityContextHolder.getContext().getAuthentication(); } @GetMapping("/me2") public Object currentUser(Authentication authentication) { return authentication; } /** * @param userDetails * @return 只包含了userDetails */ @GetMapping("/me3") public Object cuurentUser(@AuthenticationPrincipal UserDetails userDetails) { return userDetails; }
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot整合SpringSecurity實現(xiàn)JWT認證的項目實踐
本文會通過創(chuàng)建SpringBoot項目整合SpringSecurity,實現(xiàn)完整的JWT認證機制,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07Springboot 使用 JSR 303 對 Controller 控制層校驗及 Service 服務(wù)層 AOP 校驗
這篇文章主要介紹了Springboot 使用 JSR 303 對 Controller 控制層校驗及 Service 服務(wù)層 AOP 校驗 使用消息資源文件對消息國際化的相關(guān)知識,需要的朋友可以參考下2017-12-12idea使用spring Initializr 快速搭建springboot項目遇到的坑
這篇文章主要介紹了idea使用spring Initializr 快速搭建springboot項目遇到的坑,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11