springsecurity基本原理
在之前的文章《springboot + spring security 基本使用及個性化登錄配置》中對springsecurity進行了簡單的使用介紹,基本上都是對于接口的介紹以及功能的實現。 這一篇文章嘗試從源碼的角度來上對用戶認證流程做一個簡單的分析。
在具體分析之前,我們可以先看看springsecurity的大概原理:
springsecurity基本原理
其實比較簡單,主要是通過一系列的filter對請求進行攔截處理。
認證處理流程說明
我們直接來看usernamepasswordauthenticationfilter
類,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
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 的構造方法
1
2
3
4
5
6
7
8
|
public usernamepasswordauthenticationtoken(object principal, object credentials) { // 設置空的權限 super ((collection) null ); this .principal = principal; this .credentials = credentials; // 設置是否通過了校驗 this .setauthenticated( false ); } |
其實usernamepasswordauthenticationtoken是繼承于authentication
,該對象在上一篇文章中有提到過,它是處理登錄成功回調方法中的一個參數,里面包含了用戶信息、請求信息等參數。
所以接下來我們看
1
|
this .getauthenticationmanager().authenticate(authrequest); |
這里有一個authenticationmanager,但是真正調用的是providermanager
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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。
- 根據我們目前所使用的usernamepasswordauthenticationtoken,provider對應的是daoauthenticationprovider。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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.用戶信息預檢查 this .preauthenticationchecks.check(user); // 3.附加的檢查(密碼檢查) this .additionalauthenticationchecks(user, (usernamepasswordauthenticationtoken)authentication); } catch (authenticationexception var7) { } // 4.最后的檢查 this .postauthenticationchecks.check(user); // 5.返回真正的經過認證的authentication return this .createsuccessauthentication(principaltoreturn, authentication, user); } |
- 去調用自己實現的userdetailsservice,返回userdetails
- 對userdetails的信息進行校驗,主要是帳號是否被凍結,是否過期等
- 對密碼進行檢查,這里調用了passwordencoder
- 檢查userdetails是否可用。
- 返回經過認證的authentication
這里的兩次對userdetails的檢查,主要就是通過它的四個返回boolean類型的方法。
經過信息的校驗之后,通過usernamepasswordauthenticationtoken
的構造方法,返回了一個經過認證的authentication。
拿到經過認證的authentication之后,會再去調用successhandler。或者未通過認證,去調用failurehandler。
認證結果如何在多個請求之間共享
再完成了用戶認證處理流程之后,我們思考一下是如何在多個請求之間共享這個認證結果的呢?
因為沒有做關于這方面的配置,所以可以聯想到默認的方式應該是在session中存入了認證結果。
那么是什么時候存放入session中的呢?
我們可以接著認證流程的源碼往后看,在通過attemptauthentication方法后,如果認證成功,會調用successfulauthentication,該方法中,不僅調用了successhandler,還有一行比較重要的代碼
1
|
securitycontextholder.getcontext().setauthentication(authresult); |
securitycontextholder是對于threadlocal的封裝。 threadlocal是一個線程內部的數據存儲類,通過它可以在指定的線程中存儲數據,數據存儲以后,只有在指定線程中可以獲取到存儲的數據,對于其他線程來說則無法獲取到數據。 更多的關于threadlocal的原理可以看看我以前的文章。
一般來說同一個接口的請求和返回,都會是在一個線程中完成的。我們在securitycontextholder中放入了authresult,再其他地方也可以取出來的。
最后就是在securitycontextpersistencefilter
中取出了authresult,并存入了session
securitycontextpersistencefilter也是一個過濾器,它處于整個security過濾器鏈的最前方,也就是說開始驗證的時候是最先通過該過濾器,驗證完成之后是最后通過。
獲取認證用戶信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
/** * 獲取當前登錄的用戶 * @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; } |
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/u013435893/article/details/79605239