概述
在进行安全配置时,不管是明确指定还是使用缺省配置,最终安全配置中都会包含以下退出登录配置信息:
- 怎样的请求是一个退出登录请求
- 这里包含两部分信息: url, http method
- 成功退出登录过程需要做哪些事情
- 也就是各种配置的
LogoutHandler
- 核心
LogoutHandler
:SecurityContextLogoutHandler
–销毁session
和SecurityContextHolder
内容
- 也就是各种配置的
- 成功退出登录后跳转到哪里
- 也就是配置中的
logoutSuccessUrl
- 也就是配置中的
基于以上配置信息,LogoutFilter
被设计用于检测用户退出登录请求,执行相应的处理工作以及退出登录后的页面跳转。
源代码解析
package org.springframework.security.web.authentication.logout;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;
public class LogoutFilter extends GenericFilterBean {
// ~ Instance fields
// ================================================================================================
private RequestMatcher logoutRequestMatcher;
private final LogoutHandler handler;
private final LogoutSuccessHandler logoutSuccessHandler;
// ~ Constructors
// ===================================================================================================
/** * Constructor which takes a LogoutSuccessHandler instance to determine the * target destination after logging out. The list of LogoutHandlers are * intended to perform the actual logout functionality (such as clearing the security * context, invalidating the session, etc.). * 缺省情况下,这里的LogoutSuccessHandler是一个SimpleUrlLogoutSuccessHandler实例, * 在退出登录成功时跳转到/。 * * 安全配置信息中还会包含对cookie,remember me 等安全机制的配置,这些机制中在用户成功退出 * 登录时也会执行一些相应的清场工作,这些工作就是通过参数handlers传递进来的。这些handlers * 中最核心的一个就是SecurityContextLogoutHandler,它会销毁session和针对当前请求的 * SecurityContextHolder中的安全上下文对象,这是真正意义上的退出登录。 */
public LogoutFilter(LogoutSuccessHandler logoutSuccessHandler,
LogoutHandler... handlers) {
this.handler = new CompositeLogoutHandler(handlers);
Assert.notNull(logoutSuccessHandler, "logoutSuccessHandler cannot be null");
this.logoutSuccessHandler = logoutSuccessHandler;
// 定义一个缺省的用户退出登录请求匹配器:
// 只要用户请求/logout而无论http method是什么,都认为是要退出登录了,
// 该缺省值通常会被安全配置覆盖,请留意
setFilterProcessesUrl("/logout");
}
// 另外一个构造函数,如果没有指定logoutSuccessHandler,而是只指定了logoutSuccessUrl,
// 该方法会根据logoutSuccessUrl构造一个logoutSuccessHandler:SimpleUrlLogoutSuccessHandler
public LogoutFilter(String logoutSuccessUrl, LogoutHandler... handlers) {
this.handler = new CompositeLogoutHandler(handlers);
Assert.isTrue(
!StringUtils.hasLength(logoutSuccessUrl)
|| UrlUtils.isValidRedirectUrl(logoutSuccessUrl),
() -> logoutSuccessUrl + " isn't a valid redirect URL");
SimpleUrlLogoutSuccessHandler urlLogoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
if (StringUtils.hasText(logoutSuccessUrl)) {
urlLogoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl);
}
logoutSuccessHandler = urlLogoutSuccessHandler;
setFilterProcessesUrl("/logout");
}
// ~ Methods
// ========================================================================================================
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (requiresLogout(request, response)) {
// 检测到用户请求了退出当前登录,现在执行退出登录逻辑
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (logger.isDebugEnabled()) {
logger.debug("Logging out user '" + auth
+ "' and transferring to logout destination");
}
this.handler.logout(request, response, auth);
// 缺省情况下,这里的LogoutSuccessHandler是一个SimpleUrlLogoutSuccessHandler实例,
// 在退出登录成功时跳转到/。
// 上面已经成功退出了用户登录,现在跳转到相应的页面
logoutSuccessHandler.onLogoutSuccess(request, response, auth);
// 注意,这里完成了用户退出登录动作和页面跳转,所以当前请求的处理任务已经结束,
// 也就是说不用再继续filter chain的执行了,直接函数返回即可。
return;
}
// 不是用户请求退出登录的情况,继续执行 filter chain 。
chain.doFilter(request, response);
}
/** * Allow subclasses to modify when a logout should take place. * 根据当前请求和安全配置检测是否用户在请求退出登录,如果是用户在请求退出登录的情况返回true, * 否则返回false * @param request the request * @param response the response * * @return true if logout should occur, false otherwise */
protected boolean requiresLogout(HttpServletRequest request,
HttpServletResponse response) {
// logoutRequestMatcher 是配置时明确指定的,或者是根据其他配置计算出来的
return logoutRequestMatcher.matches(request);
}
// 配置阶段会将用户明确指定的logoutRequestMatcher或者根据其他配置计算出来的logoutRequestMatcher
// 通过该方法设置到当前Filter对象
public void setLogoutRequestMatcher(RequestMatcher logoutRequestMatcher) {
Assert.notNull(logoutRequestMatcher, "logoutRequestMatcher cannot be null");
this.logoutRequestMatcher = logoutRequestMatcher;
}
// 调用该方法则会将当前Filter的logoutRequestMatcher设置为一个根据filterProcessesUrl计算出来的
//AntPathRequestMatcher,该matcher会仅根据请求url进行匹配,而不管http method是什么
//
// 在该Filter的构造函数中就调用了该方法setFilterProcessesUrl("/logout"),从而构建了一个缺省的
// AntPathRequestMatcher,表示只要用户访问 url /logout,不管http method是什么,都认为用户想要
// 退出登录。但实际上,该初始值都会被配置过程中根据用户配置信息计算出的AntPathRequestMatcher
// 调用上面的setLogoutRequestMatcher(logoutRequestMatcher)覆盖该matcher
public void setFilterProcessesUrl(String filterProcessesUrl) {
this.logoutRequestMatcher = new AntPathRequestMatcher(filterProcessesUrl);
}
}