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

SpringSecurity基本原理
其實(shí)比較簡(jiǎn)單,主要是通過(guò)一系列的Filter對(duì)請(qǐng)求進(jìn)行攔截處理。
認(rèn)證處理流程說(shuō)明
我們直接來(lái)看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)證。
不過(guò)我們可以先看看UsernamePasswordAuthenticationToken 的構(gòu)造方法
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
// 設(shè)置空的權(quán)限
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
// 設(shè)置是否通過(guò)了校驗(yàn)
this.setAuthenticated(false);
}
其實(shí)UsernamePasswordAuthenticationToken是繼承于Authentication,該對(duì)象在上一篇文章中有提到過(guò),它是處理登錄成功回調(diào)方法中的一個(gè)參數(shù),里面包含了用戶信息、請(qǐng)求信息等參數(shù)。
所以接下來(lái)我們看
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);
}
}
}
- 這里首先通過(guò)provider判斷是否支持當(dāng)前傳入進(jìn)來(lái)的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)過(guò)認(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é),是否過(guò)期等
- 對(duì)密碼進(jìn)行檢查,這里調(diào)用了PasswordEncoder
- 檢查UserDetails是否可用。
- 返回經(jīng)過(guò)認(rèn)證的Authentication
這里的兩次對(duì)UserDetails的檢查,主要就是通過(guò)它的四個(gè)返回boolean類型的方法。
經(jīng)過(guò)信息的校驗(yàn)之后,通過(guò)UsernamePasswordAuthenticationToken的構(gòu)造方法,返回了一個(gè)經(jīng)過(guò)認(rèn)證的Authentication。
拿到經(jīng)過(guò)認(rèn)證的Authentication之后,會(huì)再去調(diào)用successHandler?;蛘呶赐ㄟ^(guò)認(rèn)證,去調(diào)用failureHandler。
認(rèn)證結(jié)果如何在多個(gè)請(qǐng)求之間共享
再完成了用戶認(rèn)證處理流程之后,我們思考一下是如何在多個(gè)請(qǐng)求之間共享這個(gè)認(rèn)證結(jié)果的呢?
因?yàn)闆](méi)有做關(guān)于這方面的配置,所以可以聯(lián)想到默認(rèn)的方式應(yīng)該是在session中存入了認(rèn)證結(jié)果。
那么是什么時(shí)候存放入session中的呢?
我們可以接著認(rèn)證流程的源碼往后看,在通過(guò)attemptAuthentication方法后,如果認(rèn)證成功,會(huì)調(diào)用successfulAuthentication,該方法中,不僅調(diào)用了successHandler,還有一行比較重要的代碼
SecurityContextHolder.getContext().setAuthentication(authResult);
SecurityContextHolder是對(duì)于ThreadLocal的封裝。 ThreadLocal是一個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類,通過(guò)它可以在指定的線程中存儲(chǔ)數(shù)據(jù),數(shù)據(jù)存儲(chǔ)以后,只有在指定線程中可以獲取到存儲(chǔ)的數(shù)據(jù),對(duì)于其他線程來(lái)說(shuō)則無(wú)法獲取到數(shù)據(jù)。 更多的關(guān)于ThreadLocal的原理可以看看我以前的文章。
一般來(lái)說(shuō)同一個(gè)接口的請(qǐng)求和返回,都會(huì)是在一個(gè)線程中完成的。我們?cè)赟ecurityContextHolder中放入了authResult,再其他地方也可以取出來(lái)的。
最后就是在SecurityContextPersistenceFilter中取出了authResult,并存入了session
SecurityContextPersistenceFilter也是一個(gè)過(guò)濾器,它處于整個(gè)Security過(guò)濾器鏈的最前方,也就是說(shuō)開(kāi)始驗(yàn)證的時(shí)候是最先通過(guò)該過(guò)濾器,驗(yàn)證完成之后是最后通過(guò)。
獲取認(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;
}
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
基于controller使用map接收參數(shù)的注意事項(xiàng)
這篇文章主要介紹了基于controller使用map接收參數(shù)的注意事項(xiàng),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
SpringBoot與spring security的結(jié)合的示例
這篇文章主要介紹了SpringBoot與spring security的結(jié)合的示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03
Java中方法優(yōu)先調(diào)用可選參數(shù)還是固定參數(shù)
這篇文章主要介紹了Java中方法優(yōu)先調(diào)用可選參數(shù)還是固定參數(shù),可選參數(shù)是?JDK?5?中新增的特性,也叫變長(zhǎng)參數(shù)或可變參數(shù),固定參數(shù)的概念恰好與可選參數(shù)相反,固定參數(shù)也就是普通的參,下文更多詳細(xì)內(nèi)容需要的小伙伴可以參考一下2022-05-05
使用SpringBoot項(xiàng)目導(dǎo)入openfeign版本的問(wèn)題
這篇文章主要介紹了使用SpringBoot項(xiàng)目導(dǎo)入openfeign版本的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Java實(shí)現(xiàn)的AES256加密解密功能示例
這篇文章主要介紹了Java實(shí)現(xiàn)的AES256加密解密功能,結(jié)合完整實(shí)例形式分析了Java實(shí)現(xiàn)AES256加密解密功能的步驟與相關(guān)操作技巧,需要的朋友可以參考下2017-02-02
Java多數(shù)據(jù)源的三種實(shí)現(xiàn)方式小結(jié)
多數(shù)據(jù)源是在一個(gè)應(yīng)用程序中配置和使用多個(gè)不同的數(shù)據(jù)庫(kù)連接,本文主要介紹了Java多數(shù)據(jù)源的三種實(shí)現(xiàn)方式小結(jié),具有一定的參考價(jià)值,感興趣的可以了解一下2025-03-03
RocketMQ獲取指定消息的實(shí)現(xiàn)方法(源碼)
這篇文章主要給大家介紹了關(guān)于RocketMQ獲取指定消息的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用RocketMQ具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08

