SpringSecurity--原理(代碼級別)

第六章 SpringSecurity-原理

6.1 認證原理-過濾器鏈的調用

1 源碼調試分析

  • 程序入口
  • 打斷點-第一批次
  • 運行調試
  • 打斷點-關鍵點

2 過濾器

<filter>

<filter-name>springSecurityFilterChain</filter-name>

<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

</filter>

<filter-mapping>

<filter-name>springSecurityFilterChain</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

3 debug走起…

3.1 初始化方法

3.2 過濾器:功能擴展的多個過濾器->責任鏈設計模式

3.3 獲取過濾器鏈中的過濾器,封裝爲虛擬的VirtualFilterChain對象,並開始執行過濾

3.4 開始一個一個的執行過濾器

3.5 自定義過濾器

6.2 認證原理-相關過濾器解釋

1.SecurityContextPersistenceFilter

過濾器鏈頭,是從 SecurityContextRepository 中取出用戶認證信息,默認實現爲 HttpSessionSecurityContextRepository,它會從 Session 中取出已認證的用戶信息,提高效率,避免每次請求都要查詢用戶認證信息

取出之後會放入 SecurityContextHolder 中,以便其它 filter 使用,SecurityContextHolder 使用 ThreadLocal(一個threadLocal只能綁定一個數據多個新建多個ThreadLocal) (其中綁定當前線程(有個map)只能給當前線程用)存儲用戶認證信息,保證線程之間信息隔離,最後再 finally 中清除該信息(多例解決線程安全問題)

2.WebAsyncManagerIntegrationFilter

提供了對 SecurityContext 和 WebAsyncManager 的集成,會把 SecurityContext 設置到異步線程,使其也能獲取到用戶上下文認證信息

3.HeaderWriterFilter

會往請求的 Header 中添加相應的信息

響應頭:

防止

請求頭:

 

CsrfFilter

跨域請求僞造過濾器,通過客戶端穿來的 token 與服務端存儲的 token 進行對比來判斷請求

4.LogoutFilter

org.springframework.security.web.authentication.logout.LogoutFilter

org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler

 

匹配URL,默認爲 /logout,匹配成功後則會用戶退出,清除認證信息,若有自己的退出邏輯,該過濾器可以關閉

5.UsernamePasswordAuthenticationFilter

登錄認證過濾器,默認是對 /login 的 POST 請求進行認證,首先該方法會調用 attemptAuthentication 嘗試認證獲取一個 Authentication 認證對象,然後通過 sessionStrategy.onAuthentication 執行持久化,也就是保存認證信息,然後轉向下一個 Filter,最後調用 successfulAuthentication 執行認證後事件

attemptAuthentication 該方法是認證的主要方法,認證基本流程爲 UserDeatilService 根據用戶名獲取到用戶信息,然後通過 UserDetailsChecker.check 對用戶狀態進行校驗,最後通過 additionalAuthenticationChecks 方法對用戶密碼進行校驗完後認證後,返回一個認證對象

6.DefaultLoginPageGeneratingFilter

當請求爲登錄請求時,生成簡單的登錄頁面,可以關閉

7.BasicAuthenticationFilter

Http Basic 認證的支持,該認證會把用戶名密碼使用 base64 編碼後放入 header 中傳輸,認證成功後會把用戶信息放入 SecurityContextHolder 中

8.RequestCacheAwareFilter

恢復被打斷時的請求

9.SecurityContextHolderAwareRequestFilter

針對 Servlet api 不同版本做一些包裝

10.AnonymousAuthenticationFilter

SecurityContextHolder 中認證信息爲空,則會創建一個匿名用戶到 SecurityContextHolder 中

11.SessionManagementFilter

與登錄認證攔截時作用一樣,持久化用戶登錄信息,可以保存到 Session 中,也可以保存到 cookie 或 redis 中

12.ExceptionTranslationFilter

異常攔截,處於 Filter 鏈條後部,只能攔截其後面的節點並着重處理 AuthenticationException(認證異常) AccessDeniedException(請求被拒絕異常) 異常

6.3 認證原理-重點UsernamePasswordAuthenticationFilter

6.3.1 UsernamePasswordAuthencationFilter源碼流程-不帶圖

1、獲取到頁面的用戶名和密碼

2、將username和password包裝成UsernamePasswordAuthenticationToken

3、獲取系統的認證管理器(AuthenticationManager)來調用authenticate方法完成認證

3.1)AuthenticationManager獲取ProviderManager來調用ProviderManager.authenticate()

3.2)ProviderManager獲取到所有的AuthenticationProvider判斷當前的提供者能否支持,如果支持provider.authenticate(authentication);

現在我們DaoAuthenticationProvider( authentication :頁面封裝用戶名和密碼的對象

3.2.1)retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);

  3.2.1.1)loadedUser = this.getUserDetailsService().loadUserByUsername(username);

(調用我們自己的UserDetailsService來去數據庫查用戶,按照用戶名查出來的用戶的詳細信息)封裝成UserDetails

3.2.2)preAuthenticationChecks.check(user);(預檢查,賬號是否被鎖定等…)

3.2.3)additionalAuthenticationChecks(附加的認證檢查)

  3.2.3.1)使用passwordEncoder. matches檢查頁面的密碼和數據庫的密碼是否一致

3.2.4)postAuthenticationChecks.check(user);(後置認證,檢查密碼是否過期)

3.2.5)createSuccessAuthentication:將認證成功信息重新封裝成UsernamePasswordAuthenticationToken

3.33.2又返回了一個新的UsernamePasswordAuthenticationToken,然後擦掉密碼

4 eventPublisher.publishAuthenticationSuccess(result);告訴所有監聽器認證成功了

6.3.2 UsernamePasswordAuthencationFilter源碼流程-帶圖

1 執行過濾器,獲取到頁面的用戶名和密碼

public class UsernamePasswordAuthenticationFilter extends  AbstractAuthenticationProcessingFilter

2 usernamepassword包裝成UsernamePasswordAuthenticationToken

3 獲取系統的認證管理器(AuthenticationManager)來調用authenticate方法完成認證(this.getAuthenticationManager().authenticate(authRequest))

3.1)、 AuthenticationManager獲取ProviderManager來調用ProviderManager.authenticate()

3.2)、 ProviderManager獲取到所有的AuthenticationProvider判斷當前的提供者能否支持,如果支持provider.authenticate(authentication);

DaoAuthenticationProvider( authentication :頁面封裝用戶名和密碼的對象)

3.2.1)、retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);

3.2.1.1)、 loadedUser = this.getUserDetailsService().loadUserByUsername(username);

(調用我們自己的UserDetailsService來去數據庫查用戶,按照用戶名查出來的用戶的詳細信息)封裝成UserDetail

3.2.2)、 preAuthenticationChecks.check(user);(預檢查,賬號是否被鎖定等…)

3.2.3)、 additionalAuthenticationChecks(附加的認證檢查)

3.2.3.1)、使用passwordEncoder. matches檢查頁面的密碼和數據庫的密碼是否一致

3.2.4)、 postAuthenticationChecks.check(user);(後置認證,檢查密碼是否過期)

3.2.5)、 createSuccessAuthentication:將認證成功的信息重新封裝成UsernamePasswordAuthenticationToken

3.3)、 3.2又返回了一個新的UsernamePasswordAuthenticationToken,然後擦掉密碼

4 eventPublisher.publishAuthenticationSuccess(result);認證成功

5 successfulAuthentication(request, response, chain, authResult);

通過調用 SecurityContextHolder.getContext().setAuthentication(...)  將 Authentication 對象賦給當前的 SecurityContext

org.springframework.security.core.context.SecurityContextHolderStrategy

ThreadLocal線程數據綁定

SecurityContextHolder.getContext();就能獲取到之前認證好的Authentication對象(UsernamePasswordAuthenticationToken)

6.3.3 斷點參考

6.4 認證原理-流程及相關類(API)

認證&授權

6.4.1 認證流程

  1. 用戶使用用戶名和密碼登錄
  2. 用戶名密碼被過濾器(默認爲 UsernamePasswordAuthenticationFilter)獲取到,封裝成 Authentication(UsernamePasswordAuthenticationToken
  3. token(Authentication實現類)傳遞給 AuthenticationManager 進行認證
  4. AuthenticationManager 認證成功後返回一個封裝了用戶權限信息的 Authentication 對象
  5. 通過調用 SecurityContextHolder.getContext().setAuthentication(...)  將 Authentication 對象賦給當前的 SecurityContext
  6. 將用戶的信息保存到當前線程上,共享起來
  7. SecurityContextHolder.getContext();就能獲取到之前認證好的Authentication對象(UsernamePasswordAuthenticationToken)

6.4.2 默認執行順序

1 UsernamePasswordAuthenticationFilter

  1. 用戶通過url:/login 登錄,該過濾器接收表單用戶名密碼
  2. 判斷用戶名密碼是否爲空
  3. 生成 UsernamePasswordAuthenticationToken
  4. 將 Authentiction 傳給 AuthenticationManager接口的 authenticate 方法進行認證處理
  5. AuthenticationManager 默認是實現類爲 ProviderManager ,ProviderManager 委託給 AuthenticationProvider 進行處理
  6. UsernamePasswordAuthenticationFilter 繼承了 AbstractAuthenticationProcessingFilter 抽象類,AbstractAuthenticationProcessingFilter 在 successfulAuthentication 方法中對登錄成功進行了處理,通過 SecurityContextHolder.getContext().setAuthentication() 方法將 Authentication 認證信息對象綁定到 SecurityContext
  7. 下次請求時,在過濾器鏈頭的 SecurityContextPersistenceFilter 會從 Session 中取出用戶信息並生成 Authentication(默認爲 UsernamePasswordAuthenticationToken),並通過 SecurityContextHolder.getContext().setAuthentication() 方法將 Authentication 認證信息對象綁定到 SecurityContext
  8. 需要權限才能訪問的請求會從 SecurityContext 中獲取用戶的權限進行驗證

2 DaoAuthenticationProvider(實現了 AuthenticationProvider)

通過 UserDetailsService 獲取 UserDetails

將 UserDetails 和 UsernamePasswordAuthenticationToken 進行認證匹配用戶名密碼是否正確

若正確則通過 UserDetails 中用戶的權限、用戶名等信息生成新的 Authentication 認證對象並返回

6.4.3 相關類

1.WebSecurityConfigurerAdapter(配置使用)

爲創建 WebSecurityConfigurer 實例提供方便的基類,重寫它的 configure 方法來設置安全細節

configure(HttpSecurity http):重寫該方法,通過 http 對象的 authorizeRequests()方法定義URL訪問權限,默認會爲 formLogin() 提供一個簡單的測試HTML頁面

configure (AuthenticationManagerBuilder auth):通過 auth 對象的方法添加身份驗證

2.SecurityContextHolder

SecurityContextHolder 用於存儲安全上下文信息(如操作用戶是誰、用戶是否被認證、用戶權限有哪些),它用 ThreadLocal 來保存 SecurityContext,者意味着 Spring Security 在用戶登錄時自動綁定到當前現場,用戶退出時,自動清除當前線程認證信息,SecurityContext 中含有正在訪問系統用戶的詳細信息

3.AuthenticationManagerBuilder

用於構建認證 AuthenticationManager 認證,允許快速構建內存認證、LDAP身份認證、JDBC身份驗證,添加 userDetailsService(獲取認證信息數據) 和 AuthenticationProvider's(定義認證方式)

4.UserDetailsService

該接口僅有一個方法 loadUserByUsernameSpring Security 通過該方法獲取用戶信息

5.UserDetails

代表了Spring Security的用戶實體類,帶有用戶名、密碼、權限特性等性質,可以自己實現該接口,供 Spring Security 安全認證使用,Spring Security 默認使用的是內置的 User

將從數據庫獲取的 User 對象傳入實現該接口的類,並獲取 User 對象的值來讓類實現該接口的方法

通過 Authentication.getPrincipal() 的返回類型是 Object,但很多情況下其返回的其實是一個 UserDetails 的實例

6.Authentication

Authentication 是一個接口,用來表示用戶認證信息,在用戶登錄認證之前相關信息(用戶傳過來的用戶名密碼)會封裝爲一個 Authentication 具體實現類對象,默認情況下爲 UsernamePasswordAuthenticationToken,登錄之後(通過AuthenticationManager認證)會生成一個更詳細的、包含權限的對象,然後把它保存在權限線程本地的 SecurityContext 中,供後續權限鑑定用

Authentication.principal可以獲取到已經認證的用戶詳細信息

 UsernamePasswordAuthenticationToken (密碼被擦除,authenticated=true)

7.GrantedAuthority

GrantedAuthority 是一個接口,它定義了一個 getAuthorities() 方法返回當前 Authentication 對象的擁有權限字符串,用戶有權限是一個 GrantedAuthority 數組,每一個 GrantedAuthority 對象代表一種用戶權限

8.AuthenticationManager

AuthenticationManager 是用來處理認證請求的接口,它只有一個方法 authenticate(),該方法接收一個 Authentication 作爲對象,如果認證成功則返回一個封裝了當前用戶權限信息的 Authentication 對象進行返回

它默認的實現是 ProviderManager,但它不處理認證請求,而是將委託給 AuthenticationProvider 列表,然後依次使用 AuthenticationProvider 進行認證,如果有一個 AuthenticationProvider 認證的結果不爲null,則表示成功(否則失敗,拋出 ProviderNotFoundException),之後不在進行其它 AuthenticationProvider 認證,並作爲結果保存在 ProviderManager

認證校驗時最常用的方式就是通過用戶名加載 UserDetails,然後比較 UserDetails 密碼與請求認證是否一致,一致則通過,Security 內部的 DaoAuthenticationProvider 就是使用這種方式

認證成功後加載 UserDetails 來封裝要返回的 Authentication 對象,加載的 UserDetails 對象是包含用戶權限等信息的。認證成功返回的 Authentication 對象將會保存在當前的 SecurityContext

9.AuthenticationProvider

AuthenticationProvider 是一個身份認證接口,實現該接口來定製自己的認證方式,可通過 UserDetailsSevice 對獲取數據庫中的數據

該接口中有兩個需要實現的方法:

Authentication authenticate(Authentication authentication):認證處理,返回一個 Authentication 的實現類則代表成功,返回 null 則爲認證失敗

supports(Class<?> aClass):如果該 AuthenticationProvider 支持傳入的 Authentication 認證對象,則返回 true ,如:return aClass.equals(UsernamePasswordAuthenticationToken.class);

10.AuthorityUtils

是一個工具包,用於操作 GrantedAuthority 集合的實用方法:

commaSeparatedStringToAuthorityList(String authorityString):從逗號分隔符中創建 GrantedAuthority 對象數組,幫我們快速創建出權限的集合

11.AbstractAuthenticationProcessingFilter

該抽象類繼承了 GenericFilterBean,是處理 form 登錄的過濾器,與 form 登錄相關的所有操作都在該抽象類的子類中進行(UsernamePasswordAuthenticationFilter 爲其子類),比如獲取表單中的用戶名、密碼,然後進行認證等操作

該類在 doFilter 方法中通過 attemptAuthentication() 方法進行用戶信息邏輯認證,認證成功會將用戶信息設置到 Session 中

12.HttpSecurity

用於配置全局 Http 請求的權限控制規則,對哪些請求進行驗證、不驗證等

常用方法:

authorizeRequests():返回一個配置對象用於配置請求的訪問限制

formLogin():返回表單配置對象,當什麼都不指定時會提供一個默認的,如配置登錄請求,還有登錄成功頁面

logout():返回登出配置對象,可通過logoutUrl設置退出url

antMatchers:匹配請求路徑或請求動作類型,如:.antMatchers("/admin/**")

addFilterBefore: 在某過濾器之前添加 filter

addFilterAfter:在某過濾器之後添加 filter

addFilterAt:在某過濾器相同位置添加 filter,不會覆蓋相同位置的 filter

hasRole:結合 antMatchers 一起使用,設置請求允許訪問的角色權限或IP,如:

.antMatchers("/admin/**").hasAnyRole("ROLE_ADMIN","ROLE_USER")

方法名        

用途

access(String)        

SpringEL表達式結果爲true時可訪問

anonymous()        

匿名可訪問

denyAll()        

用戶不可以訪問

fullyAuthenticated()        

用戶完全認證訪問(非remember me下自動登錄)

hasAnyAuthority(String)        

參數中任意權限可訪問

hasAnyRole(String)        

參數中任意角色可訪問

hasAuthority(String)        

某一權限的用戶可訪問

hasRole(String)        

某一角色的用戶可訪問

permitAll()        

所有用戶可訪問

rememberMe()        

允許通過remember me登錄的用戶訪問

authenticated()        

用戶登錄後可訪問

hasIpAddress(String)        

用戶來自參數中的IP可訪問

13.PasswordEncoder

Spring 提供的一個用於對密碼加密的接口,首選實現類爲 BCryptPasswordEncoder

14.BCryptPasswordEncoder

spring security中的BCryptPasswordEncoder方法採用SHA-256 +隨機鹽+密鑰  對密碼進行加密。SHA系列是Hash算法,不是加密算法,使用加密算法意味着可以解密(這個與編碼/解碼一樣),但是採用Hash處理,其過程是不可逆的。

(1)加密(encode):註冊用戶時,使用SHA-256+隨機鹽+密鑰把用戶輸入的密碼進行hash處理,得到密碼的hash值,然後將其存入數據庫中。

(2)密碼匹配(matches):用戶登錄時,密碼匹配階段並沒有進行密碼解密(因爲密碼經過Hash處理,是不可逆的),而是使用相同的算法把用戶輸入的密碼進行hash處理,得到密碼的hash值,然後將其與從數據庫中查詢到的密碼hash值進行比較。如果兩者相同,說明用戶輸入的密碼正確。

這正是爲什麼處理密碼時要用hash算法,而不用加密算法。因爲這樣處理即使數據庫泄漏,黑客也很難破解密碼

 

6.5 授權原理-AOP-MethodSecurityInterceptor

MethodSecurityInterceptor(基於AOP模式進行權限檢查)

 * 授權(權限檢查機制)採用AOP機制:

org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor

 * 方法執行前org.springframework.security.access.AccessDecisionManager通過投票機制決定這個方法是否可以被執行

6.5.1 例如

@PreAuthorize(value="hasRole('學徒') AND hasAuthority('luohan')")

@GetMapping("/level1/1")

public String leve1Page1(){

return "/level1/1";

}

6.5.2 攔截器invoke方法

 

6.5.3 支持各種功能的投票器

6.5.4 投票器標識

6.5.5 AccessDecisionManager利用系統中AccessDecisionVoter(投票器)進行授權操作:

1AffirmativeBased:有一個拒絕都不行

2ConsensusBased:贊成票數大於拒絕即可

3UnanimousBased:至少有一個贊成,不能全棄權和任何一個拒絕

 

發佈了241 篇原創文章 · 獲贊 87 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章