Spring Security

Zhang_Xiang發表於2022-05-01

Spring Security

title:version
5.6.x

Spring Security 是一個提供認證、授權以及一些常見漏洞防護的框架。該框架為 Servlet 應用程式(Spring MVC)和響應式應用程式(Spring WebFlux,本文不表)提供防護,並作為防護 spring 專案的事實標準。

Servlet 應用程式

Spring Security 通過標準的 Servlet 過濾器 整合 Servlet 容器。這表明只要在 Servlet 容器中執行的應用程式都可以使用該框架。具體來說,你不需要在以 Servlet 為容器的應用程式中專門引入 Spring,就可以使用該框架。

在 Spring Boot 專案中使用 Spring Security

新建專案

build.gradle 中輸入以下內容:

plugins {  
    id 'org.springframework.boot' version '2.6.7'  
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'  
    id 'java'  
}  
  
group = 'com.github.toy'  
version = '0.0.1-SNAPSHOT'  
sourceCompatibility = '17'  
  
repositories {  
    mavenCentral()  
}  
  
dependencies {  
    implementation 'org.springframework.boot:spring-boot-starter-security'  
    implementation 'org.springframework.boot:spring-boot-starter-web'  
    testImplementation 'org.springframework.boot:spring-boot-starter-test'  
    testImplementation 'org.springframework.security:spring-security-test'  
}  
  
tasks.named('test') {  
    useJUnitPlatform()  
}

執行

控制檯輸出

...
2022-04-30 16:21:28.626  WARN 37347 --- [           main] .s.s.UserDetailsServiceAutoConfiguration : 

Using generated security password: fceb50df-05cf-40e4-a4c5-67d5da55a6b4
...

Spring Boot Auto Configuration

Spring Boot 自動處理以下內容:

  • 啟用 Spring Security 的預設配置,它會建立一個 servlet 過濾器 bean,叫做 springSecurityFilterChain。這個 bean 負責應用程式中的所有安全工作(保護應用程式的 URL,驗證提交的使用者名稱和密碼,重定向登入表單,等等)。
  • 使用者名稱(username)為 user,密碼為 console 中生成的隨機密碼,使用此賬號和密碼建立 UserDetailsService bean。
  • 通過 Servlet 容器為每個請求註冊名為 springSecurityFilterChain過濾器

Spring Boot 配置的內容不多,但是做的很多。功能總結如下:

Spring Security 在 Servlet 應用程式中的頂層架構

我們將基於認證、授權、Protection Against Exploits 來理解這種頂層架構

過濾器

Spring Security 的 Servlet 建立在 Servlet 的 過濾器 上,所以先檢視 過濾器 的作用很有幫助。以下圖片顯示了單個請求的處理層級。

客戶端給應用程式傳送請求,容器建立一個包含過濾器ServletFilterChain ,它將處理基於請求 URI 路徑的 HttpServletRequest。在 Spring MVC 應用程式中,ServletDispatcherServlet 的例項。一個 Servlet 最多隻能處理一個 HttpServletRequestHttpServletReponse。不過,可以使用多個 過濾器 處理以下內容:

  • 阻止下游 過濾器 或者 Servlet 被呼叫。在這個例項中,過濾器 將寫 HttpServletReponse
  • 修改下游 過濾器ServletHttpServletRequestHttpServletReponse

FilterChain 的能力來自於傳入其中的 過濾器

FilterChain 用例

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
	// 在應用程式處理前,做一些邏輯處理
    chain.doFilter(request, response); // 調起應用程式處理
    // 在應用程式處理後,做一些邏輯處理
}

因為 過濾器 隻影響下游的 過濾器Servlet,所以每個 過濾器 的呼叫順序及其重要。

DelegatingFilterProxy

Spring 提供一個叫做 DelegatingFilterProxy 的實現,它橋接了 Servlet 容器的生命週期和 Spring 的 ApplicationContext。Servlet 容器允許其使用自己的標準註冊 過濾器,但是它感應不到 Spring 定義的 bean。DelegatingFilterProxy 可以憑藉 Servlet 容器的機制完成註冊,但是所有的工作都委託給實現了 過濾器 的 Spring Bean。

以下是 DelegatingFilterProxy 如何融合 過濾器FilterChain 的圖。

DelegatingFilterProxyApplicationContext 中查詢並呼叫 Filter0 Bean。DelegatingFilterProxy 的虛擬碼如下。

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
	// 載入註冊為 Spring Bean 的過濾器
	// 例如在 DelegatingFilterProxy 中,delegate 是 Filter0 Bean 的例項
	Filter delegate = getFilterBean(someBeanName);
	// 將工作委託給 Spring Bean
	delegate.doFilter(request, response);
}

DelegatingFilterProxy 的另一個好處是可以延遲查詢 過濾器 的例項。這種方式十分重要,原因是容器需要在啟動之前註冊 過濾器。不過,Spring 一般通過 ContextLoaderListener 載入 Spring 的 Bean,直到所有的 過濾器 註冊完才結束。

FilterChainProxy

對 Spring Security 的 Servlet 支援,包含在 FilterChainProxy 中。FilterChainProxy 是 Spring Security 提供的一種特殊 過濾器,它可以通過 SecurityFilterChain 委託很多 過濾器。因為 FilterChainProxy 是一個 Bean,所以它一般封裝在 DelegatingFilterProxy 中。

SecurityFilterChain

SecurityFilterChainFilterChainProxy 用於決定在該請求種哪個 Spring Security 的過濾器應該被呼叫。

Security Filters 在 SecurityFilterChain 一般以 Bean 的方式存在,它們通過 FilterChainProxy 註冊,從而替代 DelegatingFilterProxyFilterChainProxy 直接通過 Servlet 容器或 DelegatingFilterProxy 註冊,提供了諸多好處。首先,它為 Spring Security 的 Servlet 提供支撐起點。因此,如果你要解決 Spring Security Servlet 的問題,在 FilterChainProxy 中新增 debug 斷點是不錯的開始。

第二,由於 FilterChainProxy 是使用 Spring Security 的重點,它可以執行一些必要的任務。例如,清除 SecurityContext,從而避免記憶體洩漏。還可以應用於 Spring Security 的 HttpFirewall,從而保護應用程式免受某些型別的攻擊。

此外,在決定何時呼叫 SecurityFilterChain 時,它提供了更多的靈活性。在 Servlet 容器中,只基於 URL 呼叫 過濾器。不過,FilterChainProxy 通過利用 RequestMatcher 介面,可以根據 HttpServletRequest 中的任何事情決定呼叫。

事實上,FilterChainProxy 可以用於決定應該呼叫哪個 SecurityFilterChain 。因此可以為應用程式的不同切片提供一個總體的分隔配置。

在上圖中,FilterChainProxy 決定應該使用哪個 SecurityFilterChain。第一個匹配的 SecurityFilterChain 會被呼叫。如果發起一個 URL 為 /api/message 的請求,它將匹配 SecurityFilterChain 0 的模式 /api/** ,所以只會呼叫 SecurityFilterChain 0 ,即使也匹配 SecurityFilterChain n 。如果發起一個 URL 為 /messages 的請求,它不匹配 SecurityFilterChain 0 的模式 /api/**,所以 FilterChainProxy 將繼續嘗試每一個 SecurityFilterChain。如果沒有匹配到,SecurityFilterChain 匹配的 SecurityFilterChain n 例項將會被呼叫。

注意,SecurityFilterChain 0 只配置了 3 個安全 過濾器。但是,SecurityFilterChain n 配置了 4 個 過濾器 例項。每個 SecurityFilterChain 可以孤立的配置,留意這一點很重要。事實上,SecurityFilterChain 可能不配置 過濾器,如果 Spring Security 要忽略某些請求。

Security Filters

Security Filters 通過 SecurityFilterChain API 插入到 [[#FilterChainProxy]] 中。[[#過濾器]] 的順序十分重要。通常,無需瞭解 Spring Security 過濾器 的順序。但是,有些時候瞭解它們排序是有益的。

以下是 Spring Security 過濾器的綜合排序:

  • ChannelProcessingFilter
  • SecurityContextPersistenceFilter
  • WebAsyncManagerIntegrationFilter
  • HeaderWriterFilter
  • CorsFilter
  • CsrfFilter
  • LogoutFilter
  • OAuth2AuthorizationRequestRedirectFilter
  • Saml2WebSsoAuthenticationRequestFilter
  • X509AuthenticationFilter
  • AbstractPreAuthenticatedProcessingFilter
  • CasAuthenticationFilter
  • OAuth2LoginAuthenticationFilter
  • Saml2WebSsoAuthenticationFilter
  • UsernamePasswordAuthenticationFilter
  • OpenIDAuthenticationFilter
  • DefaultLoginPageGeneratingFilter
  • DefaultLogoutPageGeneratingFilter
  • ConcurrentSessionFilter
  • DigestAuthenticationFilter
  • BearerTokenAuthenticationFilter
  • BasicAuthenticationFilter
  • RequestCacheAwareFilter
  • SecurityContextHolderAwareRequestFilter
  • JaasApiIntegrationFilter
  • RememberMeAuthenticationFilter
  • AnonymousAuthenticationFilter
  • OAuth2AuthorizationCodeGrantFilter
  • SessionManagementFilter
  • ExceptionTranslationFilter
  • FilterSecurityInterceptor
  • SwitchUserFilter

處理 Security 異常

ExceptionTranslationFilter 可以將 AccessDeniedExceptionAuthenticationException 轉入 HTTP 響應中。

ExceptionTranslationFilter 作為 Security Filters 中一員插入到 [[#FilterChainProxy]] 中。

  1. 首先,ExceptionTranslationFilter 呼叫 Filter.doFilter(requeset,response) 以執行其餘應用程式。
  2. 如果使用者未認證或者是 AuthenticationException ,那麼開始認證。
    1. 清除 SecurityContextHolder
    2. HttpServletRequest 儲存在 RequestCache 中。當使用者成功認證後,RequestCache 用於重現原始請求。
    3. AuthenticationEntryPoint 用於來自客戶端的請求憑證。例如,重定向到登入頁面或者傳送 WWW-Authenticate 標頭檔案。
  3. 另外,如果是 AccessDenieException,那麼將拒絕訪問。將喚起 AccessDeniedHandler 處理拒絕訪問。
title: note
如果應用程式沒有丟擲 `AccessDeniedException` 或者 `AuthenticationException`,那麼 `ExceptionTranslationFilter` 什麼都不做。

ExceptionTranslationFilter 的虛擬碼如下:

try {
	filterChain.doFilter(request, response);
} catch (AccessDeniedException | AuthenticationException ex) {
	if (!authenticated || ex instanceof AuthenticationException) {
		startAuthentication();
	} else {
		accessDenied();
	}
}

相關文章