Spring Security 4 退出後再登入,頁面停留在登入頁,Url卻多了一個Logout引數
spring security 4 的logout問題
今天在整合spring-boot 和 spring-security的時候,出現瞭如下的怪象:
配置好了基本的登入和身份驗證(自定義了一個簡單的UserDetailsService),springboot的配置如下:
http.authorizeRequests().anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/signin" ).permitAll()
.failureUrl("/signin?error=1").permitAll()
.defaultSuccessUrl("/index")
.loginProcessingUrl("/login")
.and()
.logout().logoutRequestMatcher(AntPathRequestMatcher("/logout", "POST"))
.deleteCookies("JSESSIONID" )
.invalidateHttpSession(true)
.and()
.httpBasic();
首次登入沒有問題,即:登入後直接進入了Java-Based配置好的index頁面。然後點選執行退出,因為啟用了CSRF策略,所有登出分為”signout -> logout”兩步。登出也OK了。但是,此時再次登入,頁面沒有進入index,而是停留在登入頁面。仔細觀察URL,發現瀏覽器位址列的URL變成了http://localhost:8080/signin?logout
。經過調查,發現是LogoutSuccessHandler出了問題。
預設情況下,spring security 4啟用了SimpleUrlLogoutSuccessHandler
來處理登出的邏輯,關鍵程式碼如下:
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
super.handle(request, response, authentication);
}
super
是AbstractAuthenticationTargetUrlRequestHandler
,其handle
方法的程式碼為:
protected void handle(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
String targetUrl = determineTargetUrl(request, response);
if (response.isCommitted()) {
logger.debug("Response has already been committed. Unable to redirect to "
+ targetUrl);
return;
}
redirectStrategy.sendRedirect(request, response, targetUrl);
}
其中,最關鍵的就是
String targetUrl = determineTargetUrl(request, response);
通過detemineTargetUrl
方法得到的targetUrl就是http://localhost:8080/signin?logout
,所以程式最後跳轉到了這個路徑。那麼問題來了,determineTargetUrl
方法怎麼會生成帶有signin/logout
的URL呢?
追根溯源,最後來到了這裡:
spring-security 4 內部使用AbstractAuthenticationFilterConfigurer
對LogoutConfigurer
進行配置,而LogoutConfigurer在內部設定了SimpleUrlLogoutSuccessHandler的defaultTargetUrl引數,具體程式碼如下:
AbstractAuthenticationFilterConfigurer的相關程式碼:
private void updateAuthenticationDefaults() {
if (loginProcessingUrl == null) {
loginProcessingUrl(loginPage);
}
if (failureHandler == null) {
failureUrl(loginPage + "?error");
}
final LogoutConfigurer<B> logoutConfigurer = getBuilder().getConfigurer(
LogoutConfigurer.class);
if (logoutConfigurer != null && !logoutConfigurer.isCustomLogoutSuccess()) {
logoutConfigurer.logoutSuccessUrl(loginPage + "?logout");
}
}
注意上面第11行,spring security 4 預設將登入頁面的url拼上一個?logout
引數作為logout成功後的跳轉URL,正式這段程式碼,導致了本文之初所述的問題
LogoutConfigurer的相關程式碼:
/**
* The URL to redirect to after logout has occurred. The default is "/login?logout".
* This is a shortcut for invoking {@link #logoutSuccessHandler(LogoutSuccessHandler)}
* with a {@link SimpleUrlLogoutSuccessHandler}.
*
* @param logoutSuccessUrl the URL to redirect to after logout occurred
* @return the {@link LogoutConfigurer} for further customization
*/
public LogoutConfigurer<H> logoutSuccessUrl(String logoutSuccessUrl) {
this.customLogoutSuccess = true;
this.logoutSuccessUrl = logoutSuccessUrl;
return this;
}
/**
* Gets the {@link LogoutSuccessHandler} if not null, otherwise creates a new
* {@link SimpleUrlLogoutSuccessHandler} using the {@link #logoutSuccessUrl(String)}.
*
* @return the {@link LogoutSuccessHandler} to use
*/
private LogoutSuccessHandler getLogoutSuccessHandler() {
LogoutSuccessHandler handler = this.logoutSuccessHandler;
if (handler == null) {
handler = createDefaultSuccessHandler();
}
return handler;
}
private LogoutSuccessHandler createDefaultSuccessHandler() {
SimpleUrlLogoutSuccessHandler urlLogoutHandler = new SimpleUrlLogoutSuccessHandler();
urlLogoutHandler.setDefaultTargetUrl(logoutSuccessUrl);
if(defaultLogoutSuccessHandlerMappings.isEmpty()) {
return urlLogoutHandler;
}
DelegatingLogoutSuccessHandler successHandler = new DelegatingLogoutSuccessHandler(defaultLogoutSuccessHandlerMappings);
successHandler.setDefaultLogoutSuccessHandler(urlLogoutHandler);
return successHandler;
}