Spring Security 實戰乾貨:實現自定義退出登入

碼農小胖哥發表於2019-10-23

logout.png

1. 前言

上一篇對 Spring Security 所有內建的 Filter 進行了介紹。今天我們來實戰如何安全退出應用程式。

2. 我們使用 Spring Security 登入後都做了什麼

這個問題我們必須搞清楚!一般登入後,服務端會給使用者發一個憑證。常見有以下的兩種:

  • 基於 Session 客戶端會存 cookie 來儲存一個 sessionId ,服務端存一個 Session
  • 基於 token 客戶端存一個 token 串,服務端會在快取中存一個用來校驗此 token 的資訊。

2. 退出登入需要我們做什麼

  1. 當前的使用者登入狀態失效。這就需要我們清除服務端的使用者狀態。
  2. 退出登入介面並不是 permitAll, 只有攜帶對應使用者的憑證才退出。
  3. 將退出結果返回給請求方。
  4. 退出登入後使用者可以通過重新登入來認證該使用者。

3. Spring Security 中的退出登入

接下來我們來分析並實戰 如何定製退出登入邏輯。首先我們要了解 LogoutFilter

3.1 LogoutFilter

通過 Spring Security 實戰乾貨:內建 Filter 全解析 我們知道退出登入邏輯是由過濾器 LogoutFilter 來執行的。 它持有三個介面型別的屬性:

  1. RequestMatcher logoutRequestMatcher 這個用來攔截退出請求的 URL
  2. LogoutHandler handler 用來處理退出的具體邏輯
  3. LogoutSuccessHandler logoutSuccessHandler 退出成功後執行的邏輯

我們通過對以上三個介面的實現就能實現我們自定義的退出邏輯。

3.2 LogoutConfigurer

我們一般不會直接操作 LogoutFilter ,而是通過 LogoutConfigurer 來配置 LogoutFilter。 你可以通過 HttpSecurity#logout() 方法來初始化一個 LogoutConfigurer 。 接下來我們來實戰操作一下。

3.2.1 實現自定義退出登入請求URL

LogoutConfigurer 提供了 logoutRequestMatcher(RequestMatcher logoutRequestMatcher)logoutUrl(Sring logoutUrl) 兩種方式來定義退出登入請求的 URL 。它們作用是相同的,你選擇其中一種方式即可。

3.2.2 處理具體的邏輯

預設情況下 Spring Security 是基於 Session 的。LogoutConfigurer 提供了一些直接配置來滿足你的需要。如下:

  • clearAuthentication(boolean clearAuthentication) 是否在退出時清除當前使用者的認證資訊
  • deleteCookies(String... cookieNamesToClear) 刪除指定的 cookies
  • invalidateHttpSession(boolean invalidateHttpSession) 是否移除 HttpSession

如果上面滿足不了你的需要就需要你來定製 LogoutHandler 了。

3.2.3 退出成功邏輯

  • logoutSuccessUrl(String logoutSuccessUrl) 退出成功後會被重定向到此 URL你可以寫一個Controller 來完成最終返回,但是需要支援 GET 請求和 匿名訪問 。 通過 setDefaultTargetUrl 方法注入到 LogoutSuccessHandler
  • defaultLogoutSuccessHandlerFor(LogoutSuccessHandler handler, RequestMatcher preferredMatcher) 用來構造預設的 LogoutSuccessHandler 我們可以通過新增多個來實現從不同 URL 退出執行不同的邏輯。
  • LogoutSuccessHandler logoutSuccessHandler 退出成功後執行的邏輯的抽象根本介面。

3.3 Spring Security 退出登入實戰

現在前後端分離比較多,退出後返回json。 而且只有使用者線上才能退出登入。否則不能進行退出操作。我們採用實現 LogoutHandlerLogoutSuccessHandler 介面這種程式設計的方式來配置 。退出請求的 url 依然通過 LogoutConfigurer#logoutUrl(String logoutUrl)來定義。

3.3.1 自定義 LogoutHandler

預設情況下清除認證資訊 (invalidateHttpSession),和Session 失效(invalidateHttpSession) 已經由內建的SecurityContextLogoutHandler 來完成。我們自定義的 LogoutHandler 會在SecurityContextLogoutHandler 來執行。

  @Slf4j
  public class CustomLogoutHandler implements LogoutHandler {
      @Override
      public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
          User user = (User) authentication.getPrincipal();
          String username = user.getUsername();
          log.info("username: {}  is offline now", username);
      }
  }複製程式碼

以上是我們實現的 LogoutHandler 。 我們可以從 logout 方法的 authentication 變數中 獲取當前使用者資訊。你可以通過這個來實現你具體想要的業務。比如記錄使用者下線退出時間、IP 等等。

3.3.2 自定義 LogoutSuccessHandler

如果我們實現了自定義的 LogoutSuccessHandler 就不必要設定 LogoutConfigurer#logoutSuccessUrl(String logoutSuccessUrl) 了。該處理器處理後會響應給前端。你可以轉發到其它控制器。重定向到登入頁面,也可以自行實現其它 MediaType ,可以是 json 或者頁面

   @Slf4j
   public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {
       @Override
       public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
           User user = (User) authentication.getPrincipal();
           String username = user.getUsername();
           log.info("username: {}  is offline now", username);
   
   
           responseJsonWriter(response, RestBody.ok("退出成功"));
       }
   
       private static void responseJsonWriter(HttpServletResponse response, Rest rest) throws IOException {
           response.setStatus(HttpServletResponse.SC_OK);
           response.setCharacterEncoding("utf-8");
           response.setContentType(MediaType.APPLICATION_JSON_VALUE);
           ObjectMapper objectMapper = new ObjectMapper();
           String resBody = objectMapper.writeValueAsString(rest);
           PrintWriter printWriter = response.getWriter();
           printWriter.print(resBody);
           printWriter.flush();
           printWriter.close();
       }
   }複製程式碼

3.3.4 自定義退出的 Spring Security 配置

為了方便除錯我 註釋掉了我們 實現的自定義登入,你可以通過 http:localhost:8080/login 來登入,然後通過 http:localhost:8080/logout 測試退出。

       @Override
          protected void configure(HttpSecurity http) throws Exception {
              http.csrf().disable()
                      .cors()
                      .and()
                      .authorizeRequests().anyRequest().authenticated()
                      .and()
  //                    .addFilterBefore(preLoginFilter, UsernamePasswordAuthenticationFilter.class)
                      // 登入
                      .formLogin().loginProcessingUrl(LOGIN_PROCESSING_URL).successForwardUrl("/login/success").failureForwardUrl("/login/failure")
                      .and().logout().addLogoutHandler(new CustomLogoutHandler()).logoutSuccessHandler(new CustomLogoutSuccessHandler());
  
          }複製程式碼

4. 總結

本篇 我們實現了 在 Spring Security 下的自定義退出邏輯。相對比較簡單,你可以根據你的業務需要來實現你的退出邏輯。有什麼疑問可以通過 關注公眾號:Felordcn 來私信提問 。相關DEMO程式碼也可以通過關注後回覆 ss04 獲取。

關注公眾號:Felordcn獲取更多資訊

個人部落格:https://felord.cn

相關文章