SpringSecurity-Authentication認證
1.認證
首先我們了解SpringSecurity的各個接口和類之間的關系,只有理清了這個組件之間的關系,才能為后續的應用打下基礎。

1.1 SecurityContextHolder 安全上下文持有者

SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication = new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
如果想獲取認證過的用戶信息,可以從SecurityContextHolder中獲取
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
1.2 Authentication 認證
在Spring Security中,身份驗證有兩個主要目的:
-
AuthenticationManager的輸入,用於提供用戶為進行身份驗證而提供的憑據。 在此場景中使用時,isAuthenticated()返回false。
-
表示當前通過身份驗證的用戶。 當前的認證可以從SecurityContext中獲取。
身份驗證包含:
-
Principal—標識用戶。 當使用用戶名/密碼進行身份驗證時,這通常是UserDetails的一個實例。
-
credentials—通常是密碼。 在許多情況下,這將在用戶身份驗證后清除,以確保不會泄漏。
-
authorys—grantauthorys為用戶被授予的高級權限。 比如是角色或權限。
從源碼上可以看到Authentication接口主要有如下6個方法

實現了Authentication接口的主要有以下8個類,其中做登錄認證最常用的就是UsernamePasswordAuthenticcationToken

1.3 AuthenticationManager 認證管理器
AuthenticationManager是定義 Spring Security 的過濾器如何執行Authentication 身份驗證的 API .

從源碼可以看出AuthenticationManager接口只包含一個authenticate()方法,它的實現類主要包括以下4個,ProviderManager是AuthenticationManager最重要的一個實現類

1.3.1 ProviderManager

ProviderManager是AuthenticationManager最常用的實現。 ProviderManager委托給AuthenticationProviders列表。 每個AuthenticationProvider都有機會表明身份驗證應該是成功的,失敗的,或者表明它不能做出決定,並允許下游的AuthenticationProvider來做出決定。 如果配置的AuthenticationProviders中沒有一個可以進行身份驗證,那么身份驗證將會失敗,並會出現一個ProviderNotFoundException異常,這是一個特殊的AuthenticationException,表明ProviderManager沒有被配置為支持所傳入的身份驗證類型
從源碼中可以看到 ProviderManager委托給AuthenticationProviders列表,即providers為ProviderManager的一個list集合的成員變量。

身份提供者AuthenticationProvider接口有如下的實現類

多個AuthenticationProviders可以被注入到ProviderManager中。 每個AuthenticationProvider執行特定類型的身份驗證。 例如,DaoAuthenticationProvider支持基於用戶名/密碼的身份驗證,而JwtAuthenticationProvider支持驗證JWT令牌。
綜上所述,以上接口與類的關系如圖所示

1.4 AuthenticationEntryPoint
AuthenticationEntryPoint是Spring Security Web一個概念模型接口,顧名思義,他所建模的概念是:“認證入口點”。它在用戶請求處理過程中遇到認證異常時,被ExceptionTranslationFilter用於開啟特定認證方案(authentication schema)的認證流程。
AuthenticationEntryPoint 用來解決匿名用戶訪問無權限資源時的異常
AccessDeineHandler 用來解決認證過的用戶訪問無權限資源時的異常
1.5 AbstractAuthenticationProcessingFilter 抽象認證處理過濾器

-
當用戶提交他們的憑據時,
AbstractAuthenticationProcessingFilter會Authentication從HttpServletRequest要進行身份驗證的 中創建一個。Authenticationcreated的類型取決於 的子類AbstractAuthenticationProcessingFilter。例如,從.csv文件中提交的用戶名和密碼UsernamePasswordAuthenticationFilter創建一個。UsernamePasswordAuthenticationToken``HttpServletRequest -
接下來,將
Authentication傳遞給AuthenticationManager要進行身份驗證的 -
如果身份驗證失敗,則失敗
- 該SecurityContextHolder中被清除出去。
RememberMeServices.loginFail被調用。如果記住我沒有配置,這是一個空操作。AuthenticationFailureHandler被調用。
-
If authentication is successful, then Success.
SessionAuthenticationStrategy收到新登錄通知。- 該認證被設置在SecurityContextHolder中。稍后將
SecurityContextPersistenceFilter保存SecurityContext到HttpSession. RememberMeServices.loginSuccess被調用。如果記住我沒有配置,這是一個空操作。ApplicationEventPublisher發布一個InteractiveAuthenticationSuccessEvent.AuthenticationSuccessHandler被調用。
1.6 用戶名和密碼認證
1.6.1 表單登陸
基於表單的登錄在 Spring Security 中是如何工作的

首先客戶端發送一個沒有認證的請求到服務器端,過濾器鏈SecurityFilterChain中的FilterSecurityInterceptor拒絕並拋出AccessDeniedException異常,異常處理過濾器ExceptionTranslationFilter攔截,進入到LoginURLAuthentictionEntryPoint處理,重定向到/login的登陸頁面,然后客戶端重新發送登陸請求,服務器返回登陸頁面。當用戶提交用戶民和密碼提交后會進入到UsernamPasswordAuthenticationFilter認證過濾器中進行處理

以上的就是我們看到用戶如何被重定向到spring security默認的登錄表單的過程。
但是在實際的項目中,很多時候我們需要使用我們自定義的登陸表單,不會使用默認的登陸表單,該如何設置呢?
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.formLogin(form -> form
.loginPage("/login")
.permitAll()
);
}
在自定義的登陸表單中請求路徑必須為POST /login請求,然后用戶名和密碼名稱必須用username和password.如果使用的是 Spring MVC,您將需要一個映射GET /login到我們創建的登錄模板的控制器.
1.6.2UserDetailsService
UserDetailsService所使用的DaoAuthenticationProvider用於檢索的用戶名,密碼和其他屬性與用戶名和密碼進行認證

通過UserDetailService從數據庫中通過userName獲取用數據后,如何進行對比確認認證成功的?

AbstractAuthenticationProcessingFilter
調用 requiresAuthentication(HttpServletRequest, HttpServletResponse) 決定是否需要進行驗證操作。
如果需要驗證,則會調用 attemptAuthentication(HttpServletRequest, HttpServletResponse) 方法。
有三種結果:
1、返回一個 Authentication 對象.配置的 SessionAuthenticationStrategy 將被調用,然后調用 successfulAuthentication(HttpServletRequest,HttpServletResponse,FilterChain,Authentication) 方法。
2、驗證時發生 AuthenticationException。unsuccessfulAuthentication(HttpServletRequest, HttpServletResponse, AuthenticationException) 方法將被調用。
3、返回Null,表示身份驗證不完整。假設子類做了一些必要的工作(如重定向)來繼續處理驗證,方法將立即返回.假設后一個請求將被這種方法接收,其中返回的Authentication對象不為空。

UsernamePasswordAuthenticationFilter
attemptAuthentication 方法是AbstractAuthenticationProcessingFilter抽象類中的一個抽象方法,而UsernamePasswordAuthenticationFilter類是它的一個實現類,實現了該方法attemptAuthentication

把form表單中的username和password封裝成一個UsernamePasswordAuthenticationToken對象,然后調用AuthenticationManager的authenticate放進行認證
ProviderManager
Spring Security中進行身份驗證的是AuthenticationManager接口,ProviderManager是它的一個默認實現,但它並不用來處理身份認證,而是委托給配置好的AuthenticationProvider,每個AuthenticationProvider會輪流檢查身份認證。檢查后或者返回Authentication對象或者拋出異常。

ProviderManager委托給AuthenticationProvider列表,AuthenticationProvider是一個接口,有認證的方法authenticate進行認證

AuthenticationProvider

實現了AuthenticationProvider接口的類有如圖所示的11個類,AbstractUserDetailsAuthenticationProvider實現了給接口的認證方法
AbstractUserDetailsAuthenticationProvider

在該方法中首先從緩存中查詢,如果沒有通過username調用我們實現的接口UserDetailsService,獲取數據庫中的用戶信息;然后進行數據庫用戶和輸入用戶數據的passwrod的比較
DaoAuthenticationProvider
登錄時用到了 DaoAuthenticationProvider ,它有一個方法additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication),此方法用來校驗從數據庫取得的用戶信息和用戶輸入的信息是否匹配。

通過以上流程就完成了用戶數據的認證過程。
