1. 前言
上一篇對 Spring Security 所有內建的 Filter 進行了介紹。今天我們來實戰如何安全退出應用程式。
2. 我們使用 Spring Security 登入後都做了什麼
這個問題我們必須搞清楚!一般登入後,服務端會給使用者發一個憑證。常見有以下的兩種:
- 基於
Session
客戶端會存cookie
來儲存一個sessionId
,服務端存一個Session
。 - 基於
token
客戶端存一個token
串,服務端會在快取中存一個用來校驗此token
的資訊。
2. 退出登入需要我們做什麼
- 當前的使用者登入狀態失效。這就需要我們清除服務端的使用者狀態。
- 退出登入介面並不是
permitAll
, 只有攜帶對應使用者的憑證才退出。 - 將退出結果返回給請求方。
- 退出登入後使用者可以通過重新登入來認證該使用者。
3. Spring Security 中的退出登入
接下來我們來分析並實戰 如何定製退出登入邏輯。首先我們要了解 LogoutFilter
。
3.1 LogoutFilter
通過 Spring Security 實戰乾貨:內建 Filter 全解析 我們知道退出登入邏輯是由過濾器 LogoutFilter
來執行的。 它持有三個介面型別的屬性:
RequestMatcher logoutRequestMatcher
這個用來攔截退出請求的URL
LogoutHandler handler
用來處理退出的具體邏輯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 配置
為了方便除錯我 註釋掉了我們 實現的自定義登入,你可以通過 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獲取更多資訊