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 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 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;
}