SpringSecurity認(rèn)證流程詳解
SpringSecurity基本原理
在之前的文章《SpringBoot + Spring Security 基本使用及個(gè)性化登錄配置》中對(duì)SpringSecurity進(jìn)行了簡單的使用介紹,基本上都是對(duì)于接口的介紹以及功能的實(shí)現(xiàn)。 這一篇文章嘗試從源碼的角度來上對(duì)用戶認(rèn)證流程做一個(gè)簡單的分析。
在具體分析之前,我們可以先看看SpringSecurity的大概原理:
SpringSecurity基本原理
其實(shí)比較簡單,主要是通過一系列的Filter對(duì)請(qǐng)求進(jìn)行攔截處理。
認(rèn)證處理流程說明
我們直接來看UsernamePasswordAuthenticationFilter
類,
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter // 登錄請(qǐng)求認(rèn)證 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { // 判斷是否是POST請(qǐng)求 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); // 進(jìn)一步驗(yàn)證 return this.getAuthenticationManager().authenticate(authRequest); } } }
在attemptAuthentication
方法中,主要是進(jìn)行username和password請(qǐng)求值的獲取,然后再生成一個(gè)UsernamePasswordAuthenticationToken 對(duì)象,進(jìn)行進(jìn)一步的驗(yàn)證。
不過我們可以先看看UsernamePasswordAuthenticationToken 的構(gòu)造方法
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) { // 設(shè)置空的權(quán)限 super((Collection)null); this.principal = principal; this.credentials = credentials; // 設(shè)置是否通過了校驗(yàn) this.setAuthenticated(false); }
其實(shí)UsernamePasswordAuthenticationToken是繼承于Authentication
,該對(duì)象在上一篇文章中有提到過,它是處理登錄成功回調(diào)方法中的一個(gè)參數(shù),里面包含了用戶信息、請(qǐng)求信息等參數(shù)。
所以接下來我們看
this.getAuthenticationManager().authenticate(authRequest);
這里有一個(gè)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判斷是否支持當(dāng)前傳入進(jìn)來的Authentication,目前我們使用的是UsernamePasswordAuthenticationToken,因?yàn)槌藥ぬ?hào)密碼登錄的方式,還會(huì)有其他的方式,比如SocialAuthenticationToken。
- 根據(jù)我們目前所使用的UsernamePasswordAuthenticationToken,provider對(duì)應(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)過認(rèn)證的Authentication return this.createSuccessAuthentication(principalToReturn, authentication, user); }
- 去調(diào)用自己實(shí)現(xiàn)的UserDetailsService,返回UserDetails
- 對(duì)UserDetails的信息進(jìn)行校驗(yàn),主要是帳號(hào)是否被凍結(jié),是否過期等
- 對(duì)密碼進(jìn)行檢查,這里調(diào)用了PasswordEncoder
- 檢查UserDetails是否可用。
- 返回經(jīng)過認(rèn)證的Authentication
這里的兩次對(duì)UserDetails的檢查,主要就是通過它的四個(gè)返回boolean類型的方法。
經(jīng)過信息的校驗(yàn)之后,通過UsernamePasswordAuthenticationToken
的構(gòu)造方法,返回了一個(gè)經(jīng)過認(rèn)證的Authentication。
拿到經(jīng)過認(rèn)證的Authentication之后,會(huì)再去調(diào)用successHandler。或者未通過認(rèn)證,去調(diào)用failureHandler。
認(rèn)證結(jié)果如何在多個(gè)請(qǐng)求之間共享
再完成了用戶認(rèn)證處理流程之后,我們思考一下是如何在多個(gè)請(qǐng)求之間共享這個(gè)認(rèn)證結(jié)果的呢?
因?yàn)闆]有做關(guān)于這方面的配置,所以可以聯(lián)想到默認(rèn)的方式應(yīng)該是在session中存入了認(rèn)證結(jié)果。
那么是什么時(shí)候存放入session中的呢?
我們可以接著認(rèn)證流程的源碼往后看,在通過attemptAuthentication方法后,如果認(rèn)證成功,會(huì)調(diào)用successfulAuthentication,該方法中,不僅調(diào)用了successHandler,還有一行比較重要的代碼
SecurityContextHolder.getContext().setAuthentication(authResult);
SecurityContextHolder是對(duì)于ThreadLocal的封裝。 ThreadLocal是一個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類,通過它可以在指定的線程中存儲(chǔ)數(shù)據(jù),數(shù)據(jù)存儲(chǔ)以后,只有在指定線程中可以獲取到存儲(chǔ)的數(shù)據(jù),對(duì)于其他線程來說則無法獲取到數(shù)據(jù)。 更多的關(guān)于ThreadLocal的原理可以看看我以前的文章。
一般來說同一個(gè)接口的請(qǐng)求和返回,都會(huì)是在一個(gè)線程中完成的。我們?cè)赟ecurityContextHolder中放入了authResult,再其他地方也可以取出來的。
最后就是在SecurityContextPersistenceFilter
中取出了authResult,并存入了session
SecurityContextPersistenceFilter也是一個(gè)過濾器,它處于整個(gè)Security過濾器鏈的最前方,也就是說開始驗(yàn)證的時(shí)候是最先通過該過濾器,驗(yàn)證完成之后是最后通過。
獲取認(rèn)證用戶信息
/** * 獲取當(dāng)前登錄的用戶 * @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)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot整合SpringSecurity實(shí)現(xiàn)JWT認(rèn)證的項(xiàng)目實(shí)踐
本文會(huì)通過創(chuàng)建SpringBoot項(xiàng)目整合SpringSecurity,實(shí)現(xiàn)完整的JWT認(rèn)證機(jī)制,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07Springboot 使用 JSR 303 對(duì) Controller 控制層校驗(yàn)及 Service 服務(wù)層 AOP 校驗(yàn)
這篇文章主要介紹了Springboot 使用 JSR 303 對(duì) Controller 控制層校驗(yàn)及 Service 服務(wù)層 AOP 校驗(yàn) 使用消息資源文件對(duì)消息國際化的相關(guān)知識(shí),需要的朋友可以參考下2017-12-12idea使用spring Initializr 快速搭建springboot項(xiàng)目遇到的坑
這篇文章主要介紹了idea使用spring Initializr 快速搭建springboot項(xiàng)目遇到的坑,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11spring boot 監(jiān)聽容器啟動(dòng)代碼實(shí)例
這篇文章主要介紹了spring boot 監(jiān)聽容器啟動(dòng)代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10Java使用Semaphore對(duì)單接口進(jìn)行限流
本篇主要講如何使用Semaphore對(duì)單接口進(jìn)行限流,主要有三種方式,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07