SpringSecurity認(rèn)證流程詳解
SpringSecurity基本原理
在之前的文章《SpringBoot + Spring Security 基本使用及個性化登錄配置》中對SpringSecurity進(jìn)行了簡單的使用介紹,基本上都是對于接口的介紹以及功能的實(shí)現(xiàn)。 這一篇文章嘗試從源碼的角度來上對用戶認(rèn)證流程做一個簡單的分析。
在具體分析之前,我們可以先看看SpringSecurity的大概原理:

SpringSecurity基本原理
其實(shí)比較簡單,主要是通過一系列的Filter對請求進(jìn)行攔截處理。
認(rèn)證處理流程說明
我們直接來看UsernamePasswordAuthenticationFilter類,
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter
// 登錄請求認(rèn)證
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);
// 進(jìn)一步驗(yàn)證
return this.getAuthenticationManager().authenticate(authRequest);
}
}
}
在attemptAuthentication方法中,主要是進(jìn)行username和password請求值的獲取,然后再生成一個UsernamePasswordAuthenticationToken 對象,進(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,該對象在上一篇文章中有提到過,它是處理登錄成功回調(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判斷是否支持當(dāng)前傳入進(jìn)來的Authentication,目前我們使用的是UsernamePasswordAuthenticationToken,因?yàn)槌藥ぬ柮艽a登錄的方式,還會有其他的方式,比如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)過認(rèn)證的Authentication
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
- 去調(diào)用自己實(shí)現(xiàn)的UserDetailsService,返回UserDetails
- 對UserDetails的信息進(jìn)行校驗(yàn),主要是帳號是否被凍結(jié),是否過期等
- 對密碼進(jìn)行檢查,這里調(diào)用了PasswordEncoder
- 檢查UserDetails是否可用。
- 返回經(jīng)過認(rèn)證的Authentication
這里的兩次對UserDetails的檢查,主要就是通過它的四個返回boolean類型的方法。
經(jīng)過信息的校驗(yàn)之后,通過UsernamePasswordAuthenticationToken的構(gòu)造方法,返回了一個經(jīng)過認(rèn)證的Authentication。
拿到經(jīng)過認(rèn)證的Authentication之后,會再去調(diào)用successHandler。或者未通過認(rèn)證,去調(diào)用failureHandler。
認(rèn)證結(jié)果如何在多個請求之間共享
再完成了用戶認(rèn)證處理流程之后,我們思考一下是如何在多個請求之間共享這個認(rèn)證結(jié)果的呢?
因?yàn)闆]有做關(guān)于這方面的配置,所以可以聯(lián)想到默認(rèn)的方式應(yīng)該是在session中存入了認(rèn)證結(jié)果。
那么是什么時候存放入session中的呢?
我們可以接著認(rèn)證流程的源碼往后看,在通過attemptAuthentication方法后,如果認(rèn)證成功,會調(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過濾器鏈的最前方,也就是說開始驗(yàn)證的時候是最先通過該過濾器,驗(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)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot整合SpringSecurity實(shí)現(xiàn)JWT認(rèn)證的項(xiàng)目實(shí)踐
本文會通過創(chuàng)建SpringBoot項(xiàng)目整合SpringSecurity,實(shí)現(xiàn)完整的JWT認(rèn)證機(jī)制,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
Springboot 使用 JSR 303 對 Controller 控制層校驗(yàn)及 Service 服務(wù)層 AOP 校驗(yàn)
這篇文章主要介紹了Springboot 使用 JSR 303 對 Controller 控制層校驗(yàn)及 Service 服務(wù)層 AOP 校驗(yàn) 使用消息資源文件對消息國際化的相關(guān)知識,需要的朋友可以參考下2017-12-12
idea使用spring Initializr 快速搭建springboot項(xiàng)目遇到的坑
這篇文章主要介紹了idea使用spring Initializr 快速搭建springboot項(xiàng)目遇到的坑,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11
spring boot 監(jiān)聽容器啟動代碼實(shí)例
這篇文章主要介紹了spring boot 監(jiān)聽容器啟動代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-10-10

