SpringSecurity過濾器原理

ins1mnia發表於2021-12-02

SpringSecurity原理

主要過濾器鏈

SpringSecurity的功能主要是由一系列的過濾器鏈相互配合完成的。驗證一個過濾器之後放行到下一個過濾器鏈,然後到最後。

認證流程

過濾器作用

  1. SecurityContextPersistenceFilter:會在每次請求處理之前從配置好的SecurityContextRepository中獲取SecurityContext安全上下文資訊,然後載入到SecurityContextHolder中,然後在該次請求處理完成之後,將SecurityContextHolder中關於這次請求的資訊儲存到一個“倉庫”中,然後將SecurityContextHolder中的資訊清除,例如在Session中維護一個使用者的安全資訊就是這個過濾器處理的。

  2. DefaultLoginPageGeneratingFilter:如果沒有配置自定義登入頁面,那系統初始化時就會配置這個過濾器,並且用於在需要進行登入時生成一個登入表單頁面。

  3. BasicAuthenticationFilter:檢測和處理http basic認證。

  4. 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);
    
  5. RequestCacheAwareFilter:用來處理請求的快取。

  6. SecurityContextHolderAwareRequestFilter:主要是包裝請求物件request。

  7. AnonymousAuthenticationFilter:檢測SecurityContextHolder中是否存在Authentication物件,如果不存在則為其提供一個匿名Authentication。

  8. SessionManagementFilter:管理Session的過濾器

  9. 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();
        }
    }
    
  10. FilterSecurityInterceptor:可以看做過濾器鏈的出口

  11. 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具體的原始碼的細節可以參考一篇部落格:

https://www.cnblogs.com/longfurcat/p/9417912.html

相關文章