SpringSecurity原理
主要過濾器鏈
SpringSecurity的功能主要是由一系列的過濾器鏈相互配合完成的。驗證一個過濾器之后放行到下一個過濾器鏈,然后到最后。
認證流程
過濾器作用
-
SecurityContextPersistenceFilter:會在每次請求處理之前從配置好的SecurityContextRepository中獲取SecurityContext安全上下文信息,然后加載到SecurityContextHolder中,然后在該次請求處理完成之后,將SecurityContextHolder中關於這次請求的信息存儲到一個“倉庫”中,然后將SecurityContextHolder中的信息清除,例如在Session中維護一個用戶的安全信息就是這個過濾器處理的。
-
DefaultLoginPageGeneratingFilter:如果沒有配置自定義登錄頁面,那系統初始化時就會配置這個過濾器,並且用於在需要進行登錄時生成一個登錄表單頁面。
-
BasicAuthenticationFilter:檢測和處理http basic認證。
-
UsernamePasswordAuthenticationFilter:用於處理基於表單的登錄請求,從表單中獲取用戶名和密碼。默認情況下處理來自/login的表單action。從表單中獲取用戶名和密碼時,默認使用的表單name屬性值為username和password,這倆個值也可以通過usernameParameter和passwordParameter在配置中自定義。
這個過濾器在表單提交登錄請求之時會起作用。那么假設現在采用SpringSecurity整合Jwt,那么我需要配置一個Jwt登錄認證類(繼承BasicAuthenticationFilter或者繼承OncePerRequestFilter都可以,因為BasicAuthenticationFilter繼承了OncePerRequestFilter),重寫過濾器方法。Jwt的token認證登錄是需要在在采用用戶名密碼登錄認證之前,所以在配置Jwt登錄認證類的時候需要在UsernamePasswordAuthenticationFilter之前添加過濾器。
//配置自定義過濾器 添加jwt登錄授權過濾器 //在過濾器UsernamePasswordAuthenticationFilter之前 http.addFilterBefore(jwtAuthenticationFilter,UsernamePasswordAuthenticationFilter.class);
-
RequestCacheAwareFilter:用來處理請求的緩存。
-
SecurityContextHolderAwareRequestFilter:主要是包裝請求對象request。
-
AnonymousAuthenticationFilter:檢測SecurityContextHolder中是否存在Authentication對象,如果不存在則為其提供一個匿名Authentication。
-
SessionManagementFilter:管理Session的過濾器
-
ExceptionTranslationFilter:捕獲來自過濾器鏈的所有異常,並進行處理。但是只處理兩類異常:AccessDeniedException和AuthenticationException 異常,其他的異常會繼續拋出。
如果捕獲到的AuthenticationException,那么將會使用其對應的AuthenticationEntryPoint的commence()方法處理。在處理之前,ExceptionTranslationFilter先使用RequestCache將當前的HTTPServletRequest的信息保存起來,方便用戶登錄成功后可以跳轉到之前的頁面。
可以自定義AuthenticationException的處理方法。需要實現AuthenticationEntryPoint接口,然后重寫commence()方法。
/** * 當未登錄或者token失效時訪問接口自定義的返回結果 */ @Component public class RestfulAuthorizationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { response.setCharacterEncoding("utf-8"); response.setContentType("application/json"); PrintWriter writer = response.getWriter(); RespBean bean = RespBean.error("請先登錄!"); bean.setCode(401); writer.write(new ObjectMapper().writeValueAsString(bean)); writer.flush(); writer.close(); } }
如果捕獲的AuthenticationDeniedException,那么將會根據當前訪問的用戶是否已經登錄認證做不同的處理,如果未登錄,則會使用關聯的AuthenticationEntryPoint的commence()方法進行處理,否則將使用關聯的AccessDeniedHandler的handle()方法進行處理。
可以進行自定義AuthenticationDeniedException的處理方法。需要實現AccessDeniedHandler接口,然后重寫handle()方法。
@Component public class RestfulAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException { response.setCharacterEncoding("utf-8"); response.setContentType("application/json"); PrintWriter writer = response.getWriter(); RespBean error = RespBean.error("權限不足,聯系管理員!"); writer.write(new ObjectMapper().writeValueAsString(error)); error.setCode(403); writer.flush(); writer.close(); } }
-
FilterSecurityInterceptor:可以看做過濾器鏈的出口
-
RememberMeAuthenticationFilter:當用戶沒有登錄而直接訪問資源時, 從 cookie 里找出用戶的信息, 如果 Spring Security 能夠識別出用戶提供的remember me cookie, 用戶將不必填寫用戶名和密碼, 而是直接登錄進入系統,該過濾器默認不開啟。
SecurityContextHolder
SecurityContext對象是安全上下文信息,包括當前使用系統的用戶的信息。每個用戶都會有它的安全上下文對象,所以把每一個用戶的SecurityContext保存到SecurityContextHolder中。
SecurityContextHolder存儲SecurityContext的方式根據應用場景不同也有區別:
(1)單機系統,即應用從開啟到關閉的整個生命周期只有一個用戶在使用。由於整個應用只需要保存一個SecurityContext(安全上下文即可)
(2)多用戶系統,比如典型的Web系統,整個生命周期可能同時有多個用戶在使用。這時候應用需要保存多個SecurityContext(安全上下文),需要利用ThreadLocal進行保存,每個線程都可以利用ThreadLocal獲取其自己的SecurityContext,及安全上下文。ThreadLocal內部會用數組來存儲多個對象的。原理是,ThreadLocal會為每個線程開辟一個存儲區域,來存儲相應的對象。
Authentication:用戶信息的表示
在SecurityContextHolder中存儲了當前與系統交互的用戶的信息。Spring Security使用一個Authentication 對象來表示這些信息。
Authentication 主要包含了:
- 用戶權限集合
- 用戶證書(密碼)
- 細節(Details)
- Principal(就是這個用戶的賬戶信息)
在自定義登錄認證過濾器的時候,記得需要把用戶的信息(Authentication )保存到SecurityContextHolder中,以便后續用戶的正常使用。比如我在做和Jwt認證的整合的時候,繼承OncePerRequestFilter,重寫doFilterInternal方法,認證完token之后,就需要把用戶的信息存入安全上下文Holder中。
UsernamePasswordAuthenticationToken authenticationToken
=new UsernamePasswordAuthenticationToken(user,null, null);
authenticationToken.setDetails(new WebAuthenticationDetailsSource()
.buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
關於SecurityContextHolder大概就這樣,有一些關於SecurityContextHolder具體的源碼的細節可以參考一篇博客: