Spring Security過濾鏈參考文檔

一、簡介

Spring Security是一個提供身份驗證,授權和保護以防止常見攻擊的框架,需要Java 8或更高版本的運行環境。

它通過使用標準的Servlet Filter來集成Servlet容器,這意味着它可以與在Servlet容器中運行的任何應用程序一起工作。

更具體地說,您無需在基於Servlet的應用程序中使用Spring即可利用Spring Security。

二、Spring Security 過濾鏈

Spring Security對Servlet的支持是基於Servlet的Filter,因此首先了解Filter是非常有幫助的。

下圖顯示了單個 HTTP 請求的處理程序的典型分層:

客戶端嚮應用程序發送請求,然後容器創建了一個FilterChain,這個FilterChain含許多的Filter和一個Servlet,

這些Filter和Servlet能夠對基於請求URI路徑的HttpServletRequest進行處理。

在Spring MVC應用中,Servlet是一個DispatcherServlet實例。

最多隻能有一個Servlet處理HttpServletRequest和HttpServletResponse,

但是Filter都可以用來阻止下游的Filter或Servlet被調用,或修改下游Filter的HttpServletRequest或HttpServletResponse。

例如:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // do something before the rest of the application
    chain.doFilter(request, response); // invoke the rest of the application
    // do something after the rest of the application
}

Spring提供了一個Filter接口的實現類(DelegatingFilterProxy),它連接了Servlet容器的生命週期和Spring的ApplicationContext。

Servlet容器允許用它自己的標準註冊過濾鏈,但是它不知道Spring定義的Bean。

DelegatingFilterProxy可以通過標準Servlet容器機制進行註冊,但是它將所有工作委託給實現了Filter接口的Bean實例。

下圖是對上文的介紹:

DelegatingFilterProxy從ApplicationContext中查找Filter的第一個Bean,然後調用該Bean。

另外,DelegatingFilterProxy允許延遲加載Filter Bean實例,因爲容器需要在啓動前註冊Filter實例。

然而,Spring通常等到Filter註冊完後才用ContextLoaderListener去加載Filter實例。

Spring Security對Servlet的支持主要靠FilterChainProxy,它是Spring Security提供的一個特別的Filter。

FilterChainProxy允許委派許多Filter實例通過SecurityFilterChain(它是一個Bean,通常被包裹在DelegatingFilterProxy中)。

如圖所示:

 FilterChainProxy使用SecurityFilterChain決定爲請求調用哪個SpringSecurity Filter。

在SecurityFilterChain中的Security Filter通常都是Bean,它們通常都是被FilterChainProxy註冊,而不是DelegatingFilterProxy。

FilterChainProxy爲直接註冊在DelegatingFilterProxy或Servlet提供了很多優勢:

1、FilterChainProxy爲Spring Security 的所有Servlet支持提供了一個入口。

2、FilterChainProxy爲決定什麼時候應該調用SecurityFilterChain提供了靈活性。

因此,如果您嘗試對 Spring Security的Servlet支持進行故障排除,則爲FilterChainProxy打斷點調試是一個很好的開始。

在Servlet容器中,Filter的調用僅僅基於URL,但FilterChainProxy通過調用RequestMatcher接口可以決定任何調用。

事實上,FilterChainProxy可以用於決定哪個SecurityFilterChain應該被調用:

若有多個SecurityFilterChain,FilterChainProxy將決定那個SecurityFilterChain應該被使用。

只有第一個匹配的SecurityFilterChain將會被調用。

如果一個請求URL是"/api/messages"的話,那麼只有SecurityFilterChain0將會被調用,甚至該URL也匹配其他的。

三、處理安全異常

ExceptionTranslationFilter允許將AccessDeniedException和AuthenticationException反應給HTTP響應。

ExceptionTranslationFilter作爲安全Filter之一被插入到FilterChainProxy。

首先ExceptionTranslationFilter調用FilterChain.doFilter(request,response)去調用剩餘的應用程序。

如果用戶沒有認證或者發生認證異常,然後就開始認證。

SecurityContextHolder被清空,HttpServletRequest保存在RequestCache中,

當認證成功後,RequestCache將重放原始請求。

AuthenticationEntryPoint用來向客戶端請求憑據,他可能是重定向到登陸頁面或者發送一個WWW-Authenticate頭部。

否則,如果是AccessDeniedException,那麼AccessDenieHandler將會被調用。

用僞代碼表示他將會是這樣:

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

四、認證

1、SecurityContextHolder:Spring Security存儲認證信息的地方。

2、SecurityContext:從SecurityContextHolder中獲取,它含有當前已通過身份認證的用戶。

3、Authentication:SecurityContext中的當前用戶,或者是用戶提供給AuthenticationManager驗證的憑證。

4、GrantedAuthority:擁有的權限。

5、AuthenticationManager:定義Spring Security的Filter如何執行身份驗證的API。

6、ProviderManager:AuthenticationManager最常見的實現類。

7、AuthenticationProvider:ProviderManager用來執行特定的身份驗證。

8、AbstractAuthenticationProcessingFilter:身份驗證的基礎。

Ⅰ、SecurityContextHolder是身份驗證的核心:

用戶經過身份驗證最簡單的方法是直接設置SecurityContextHolder(存儲認證信息的地方)。

SecurityContext context = SecurityContextHolder.createEmptyContext(); 
Authentication authentication =
    new TestingAuthenticationToken("username", "password", "ROLE_USER"); 
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context); 

1、創建一個新的SecurityContext實例而不是用SecurityContextHolder.getContext().setAuthentication(authentication)

是很重要的,它可以避免多線程下的竟態條件。

2、Spring Security不關心在SecurityContext上關於Authentication什麼類型的實現,這裏使用的是TestingAuthenticationToken,

是因爲它很簡單,通常使用的都是UsernamePasswordAuthenticationToken(userDetails,password,authorities)

3、最後,在SecurityContextHolder上設置SecurityContext,Spring Security將會使用認證信息。

如果你想要獲取驗證信息,你可以像下面這樣做:

SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

默認情況下,SecurityContextHolder使用ThreadLocal去存儲這些信息。

這意味着對於相同線程下的方法,甚至不將它作爲參數傳遞給其他方法,SecurityContext也是可用的。

如果處理當前主體的請求後想清除線程,這種情況下也是非常安全的。

因爲Spring Security的FilterChainProxy確保SecurityContext總是被清除的。

Ⅱ、Authentication在Spring Security中有兩個主要用途:

1、作爲AuthenticationManager中的輸入提供身份驗證的憑據。

2、代表當前認證通過的用戶,可以從SecurityContext中獲取。

Authentication包含如下:

principal:標識用戶。使用用戶名/密碼進行身份驗證時,這通常是UserDetails實例。

credentials:通常是密碼。在許多情況下,這將在用戶經過身份驗證後被清除,以確保它不被泄露。

authorities:授予用戶的高級別權限。

Ⅲ、ProviderManager委託了一個AuthenticationProvider列表,每個AuthenticationProvider都有機會認證。

AuthenticationProvider可以提供一個特定的身份認證,例如:

DaoAuthenticationProvider通過用戶名密碼身份驗證,JwtAuthenticationProvider通過JWT token身份認證。

ProviderManager還允許配置一個可選的父級AuthenticationManager,

如果沒有一個AuthenticationProvider能夠進行認證,就向它進行諮詢,它通常是ProviderManager的實例。

實際上,多個ProviderManager實例可能共享相同的父級AuthenticationManager。

這是很常見的情況:多個SecurityFilterChain實例有相同的身份認證(相同的父級AuthenticaManager),

但是不同的驗證機制(不用的ProviderManaer實例)。

默認情況下,ProviderManager將會試圖清除認證成功的Authentication中的敏感信息,

這樣可以防止密碼之類的信息被保留的時間超過HttpSession所需的時間。

 Ⅳ、AuthenticationEntryPoint用於發送從客戶端請求憑據的 HTTP 響應。

有時客戶端會通過用戶名密碼去請求資源,在這種情況下,Spring Security不需要提供從客戶端請求憑據的HTTP 響應。

其他情況下,客戶端向它們無權訪問的資源發出未經身份驗證的請求,它的實現類通常用於請求用戶憑據。

例如:重定向到登錄頁,用www-Authenticate頭部進行響應。

 Ⅴ、AbstractAuthenticationProcessingFilter是最基礎的用戶身份憑據認證Filter。

在用戶憑據被認證之前,Spring Security通常用AuthenticationEntryPoint請求用戶憑據。

接下來,AbstractAuthenticationProcessingFilter能夠驗證任何提交給它的身份驗證請求。

1、當用戶提交憑據時,AbstractAuthenticationProcessingFilter從HttpServletRequest創建Authentication去驗證,

被創建的Authentication依賴於AbstractAuthenticationProcessingFilter的子類。例如:UsernamePasswordAuthenticationFilter

通過提交給HttpServletRequest的用戶名和密碼來創建一個UsernamePasswordAuthenticationToken。

2、接下來這個Authentication被傳遞給AuthenticationManager去驗證,如果驗證失敗:

那麼SecurityContextHolder會被清空,RememberMeServices.loginFail會被調用,AuthenticationFailureHandler會被調用。

如果驗證成功:

SessionAuthenticationStrategy將會收到新的登錄通知,這個Authentication將會被設置到SecurityContextHolder中,

然後SecurityContextPersistenceFilter保存SecurityContext到HttpSession中。

RememberMeServices.loginSuccess被調用,如果沒有配置記住我,它將會是no-op。

ApplicationEventPublisher發佈InteractiveAuthenticationSuccessEvent,最後AuthenticationSuccessHandler被調用。

 Ⅵ、Username/Password Authentication

Spring Security提供了以下內置機制,用於從HttpServletRequest中讀取用戶名和密碼。

Form Login、Basic Authentication、Digest Authentication。

以Form Login爲例:

用戶向未經授權的資源發出未經身份驗證的請求"/private",

Spring Security的FilterSecurityInterceptor拋出AccessDeniedException異常提示該請求沒有經過認證。

由於用戶沒有通過身份驗證,ExceptionTranslationFilter通過AuthenticationEntryPoint發起身份認證併發送一個重定向到登錄頁。

大多數情況下,AuthenticationEntryPoint是LoginUrlAuthenticationEntryPoint的實例。

然後,瀏覽器將請求將其重定向到的登錄頁面,當用戶名和密碼被提交後:

UsernamePasswordAuthenticationFilter就開始驗證用戶名和密碼,

UsernamePasswordAuthenticationFilter繼承於AbstractAuthenticationProcessingFilter,所以下圖看起來會很相似:

當用戶提交用戶名密碼後,UsernamePasswordAuthenticationFilter創建一個UsernamePasswordAuthenticationToken,

它是從一個從HttpServletRequest抽取用戶名和密碼創建的一個Authentication類型。

接下來UsernamePasswordAuthenticationToken將會被傳遞給AuthenticationManager去認證。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章