一、簡介
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去認證。