瞭解SpringSecurity更多功能前,咱們先了解下其認證流程源碼 前端
咱們在前面幾節中說了SpringSecurity的個性化用戶認證流程:
自定義用戶認證邏輯:
spring
咱們上面都是去實現Spring給咱們的接口,好比自定義用戶認證邏輯:
實現UserDetails、UserDetailsService、接口;
在個性化用戶認證流程時候實現SpringSecurity自帶的:登陸失敗實現:AuthenticationFailureHandler;登陸成功實現:
AuthenticationSuccessHandler接口。
目前頭腦裏就是碎片化的登陸認證流程,只知道登陸成功怎麼作,登陸失敗後如何作?可是在Spring裏面是怎樣把全部邏輯串起來的?瀏覽器
認證流程源碼級詳解主要講解如下3點:微信
咱們以表單認證爲例:從發起認證請求到認證過濾器,接着認證成功後,響應從認證過濾器返回的整個過程。SpringSecurity作了什麼,設計到了哪些類?他們之間如何調用?
SpringSecurity認證流程中涉及到的主要的類和接口以下:
session
咱們按照上面圖示剖析源碼: app
應用debug啓動,瀏覽器登陸:
ide
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);函數
super((Collection)null);//調用父類構造函數;
ui
AuthenticationManager:自身並不包含驗證邏輯,做用用來管理下面的AuthenticationProvider;AuthenticationManager下面有不少實現類,最後是在
ProviderManager:
this
ProviderManager在public Authentication authenticate(Authentication authentication)會進行for循環,獲取全部AuthenticationProvider,全部校驗邏輯是在AuthenticationProvider裏面的,爲何這裏是一個集合:是由於不一樣的登陸方式其認證邏輯是不同的,咱們如今是用戶名密碼登陸,是須要去校驗密碼。若是是微信登陸,則又是不同的。AuthenticationManager做用就是把全部AuthenticationProvider蒐集起來,認證時候,挨個去問,你當前的provider支不支持我如今的的登陸方式(其實就是作循環,而後調用supports方法)。
而後進入DaoAuthenticationProvider,DaoAuthenticationProvider會調用其父類的AbstractUserDetailsAuthenticationProvider的
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider
authenticate方法
這裏和咱們自定義認證邏輯上符合了,咱們自定義認證邏輯時候就是實現了一個UserDetailsService接口。
預檢查---->檢查UserDetails的3個boolean:
附加檢查:在DaoAuthenticationProvider--->檢查密碼是否匹配
而後進行後檢查:後檢查主要檢查4個boolean中最後一個:
若是上面檢驗所有沒問題的話,就認爲認證是合法的,而後咱們再建立一個已經受權的Authentication
最後就會按照下圖鏈返回:一直到:UsernamePasswordAuthenticationFilter
再往下: AbstractAuthenticationProcessingFilter
登陸成功以後走到咱們自定義的成功處理器:
SecurityContextHolder.getContext().setAuthentication(authResult);
自定義成功處理器:
登陸成功後:把認證信息打印出去:
response.getWriter().write(objectMapper.writeValueAsString(authentication));
try-catch補貨時候,若是有異常拋出就會執行:
多個請求共享確定是放到session裏,那麼SpringSecurity是何時?什麼東西放到了session裏面?何時又從session裏面讀取出來的?
在這個過程當中涉及到以下右邊類:
AbstractAuthenticationProcessingFilter裏面successfulAuthentication有一個:
SecurityContextHolder.getContext().setAuthentication(authResult);
實際上是把咱們認證成功的Authentication放到咱們的: SecurityContext裏面,而後SecurityContext放到SecurityContextHolder裏面。
SecurityContext其實很簡單,他就是一個接口,其惟一的實現類是:
package org.springframework.security.core.context; import org.springframework.security.core.Authentication; public class SecurityContextImpl implements SecurityContext { private static final long serialVersionUID = 420L; private Authentication authentication; public SecurityContextImpl() { } public boolean equals(Object obj) { if (obj instanceof SecurityContextImpl) { SecurityContextImpl test = (SecurityContextImpl)obj; if (this.getAuthentication() == null && test.getAuthentication() == null) { return true; } if (this.getAuthentication() != null && test.getAuthentication() != null && this.getAuthentication().equals(test.getAuthentication())) { return true; } } return false; } public Authentication getAuthentication() { return this.authentication; } public int hashCode() { return this.authentication == null ? -1 : this.authentication.hashCode(); } public void setAuthentication(Authentication authentication) { this.authentication = authentication; } public String toString() { StringBuilder sb = new StringBuilder(); sb.append(super.toString()); if (this.authentication == null) { sb.append(": Null authentication"); } else { sb.append(": Authentication: ").append(this.authentication); } return sb.toString(); } }
SecurityContext說明:咱們看SecurityContextImpl源碼知道,其實他就是對Authentication的包裝,而且重寫其hashCode和equals,保證其惟一性。
SecurityContextHolder:他其實是ThreaadLocal封裝。ThreaadLocal是跟線程綁定的一個Map,在這個線程裏面存放的東西能夠在另外一個線程讀取出來,你能夠理解爲:線程的全局變量。
最後:SecurityContextHolder會交給SecurityContextPersistenceFilter過濾器:他的位置在過濾器鏈的最前端:
SecurityContextPersistenceFilter過濾器做用:
這樣不一樣的請求能夠從線程裏面拿到認證信息。拿到之後放到線程裏面,由於請求和響應是在一個線程中。
咱們如何用SecurityContext獲取用戶信息。咱們在Spring-Security-demo 裏面的controller裏面獲取用戶信息
瀏覽器訪問:
咱們也能夠經過SpringMvc本身作的注入查找,咱們修改以下:
也能夠要求放回用戶詳情信息,其餘信息不要了。