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

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

1. 前言

上一篇對 Spring Security 所有內置的 https://www.felord.cn/spring-security-filters.html 進行了介紹。今天我們來實戰如何安全退出應用程序。

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

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

  • 基於 Session 客戶端會存 cookie 來保存一個 sessionId ,服務端存一個 Session 。
  • 基於 token 客戶端存一個 token 串,服務端會在緩存中存一個用來校驗此 token 的信息。

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

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

3. Spring Security 中的退出登錄

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

3.1 LogoutFilter

通過 https://www.felord.cn/spring-security-filters.html 我們知道退出登錄邏輯是由過濾器 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。 而且只有用戶在線才能退出登錄。否則不能進行退出操作。我們採用實現 LogoutHandler 和 LogoutSuccessHandler 接口這種編程的方式來配置 。退出請求的 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 配置

為了方便調試我 註釋掉了我們 https://www.felord.cn/spring-security-login.html,你可以通過 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 獲取。


分享到:


相關文章: