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 配置的內容不多,但是做的很多。功能總結如下:
- 與應用程式的任何互動都需要經過身份驗證的使用者
- 為你生成預設登入表單
- 使用使用者名稱
user
和在 console 中生成的密碼,基於表單做身份認證(在剛才 console 中輸出的密碼為:fceb50df-05cf-40e4-a4c5-67d5da55a6b4
) - 通過 BCrypt 加密密碼,以此保護密碼
- 使用者可以退出登入
- 防止 CSRF(Cross-site request forgery,跨站請求偽造) 攻擊
- Session Fixation 防護
- 整合安全頭
- 用於防護請求的 HTTP Strict Transport Security
- 整合 X-Content-Type-Options
- 控制快取(你的應用程式可以重寫快取,從而快取你的靜態資源)
- 整合 X-XSS-Protection
- 整合 X-Frame-Options 以阻止 Clickjacking
- 整合以下 Servlet API:
Spring Security 在 Servlet 應用程式中的頂層架構
我們將基於認證、授權、Protection Against Exploits 來理解這種頂層架構。
過濾器
Spring Security 的 Servlet 建立在 Servlet 的 過濾器
上,所以先檢視 過濾器
的作用很有幫助。以下圖片顯示了單個請求的處理層級。
客戶端給應用程式傳送請求,容器建立一個包含過濾器
和 Servlet
的 FilterChain
,它將處理基於請求 URI 路徑的 HttpServletRequest
。在 Spring MVC 應用程式中,Servlet
是 DispatcherServlet
的例項。一個 Servlet
最多隻能處理一個 HttpServletRequest
和 HttpServletReponse
。不過,可以使用多個 過濾器
處理以下內容:
- 阻止下游
過濾器
或者Servlet
被呼叫。在這個例項中,過濾器
將寫HttpServletReponse
。 - 修改下游
過濾器
和Servlet
的HttpServletRequest
和HttpServletReponse
。
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
的圖。
DelegatingFilterProxy
從 ApplicationContext
中查詢並呼叫 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
SecurityFilterChain
被 FilterChainProxy
用於決定在該請求種哪個 Spring Security 的過濾器應該被呼叫。
Security Filters 在 SecurityFilterChain
一般以 Bean 的方式存在,它們通過 FilterChainProxy
註冊,從而替代 DelegatingFilterProxy
。FilterChainProxy
直接通過 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
可以將 AccessDeniedException
和 AuthenticationException
轉入 HTTP 響應中。
ExceptionTranslationFilter
作為 Security Filters 中一員插入到 [[#FilterChainProxy]] 中。
- 首先,
ExceptionTranslationFilter
呼叫Filter.doFilter(requeset,response)
以執行其餘應用程式。 - 如果使用者未認證或者是
AuthenticationException
,那麼開始認證。- 清除
SecurityContextHolder
。 HttpServletRequest
儲存在RequestCache
中。當使用者成功認證後,RequestCache
用於重現原始請求。AuthenticationEntryPoint
用於來自客戶端的請求憑證。例如,重定向到登入頁面或者傳送WWW-Authenticate
標頭檔案。
- 清除
- 另外,如果是
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();
}
}