Spring Security 源码之 LogoutFilter
2020-11-18 本文已影响0人
AlienPaul
LogoutFilter
负责处理用户登出操作。内部包含LogoutHandler
和LogoutSuccessHandler
。其中LogoutHandler
负责清除用户登录状态信息,LogoutSuccessHandler
负责执行登出成功之后的其他操作,例如页面跳转等。
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 判断是否适用于logout filter
// 使用RequestMatcher检查请求是否是logout请求
if (requiresLogout(request, response)) {
// 获取认证信息
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Logging out [%s]", auth));
}
// 使用handler处理登出流程
this.handler.logout(request, response, auth);
// 调用登出成功句柄,运行之后的逻辑
this.logoutSuccessHandler.onLogoutSuccess(request, response, auth);
return;
}
chain.doFilter(request, response);
}
LogoutHandler
LogoutHandler
具有多个子类,分别对应了清除多种类型认证信息状态的逻辑。
LogoutHandler
的子类如下:
- CookieClearingLogoutHandler:清除cookie
- CsrfLogoutHandler:清除csrf token
- HeaderWriterLogoutHandler:登出时添加header到response。不同的HeadWriter可实现不同的写header逻辑
- SecurityContextLogoutHandler:清除SecurityContext,同时让session失效
- AbstractRememberMeServices:清除rememberMe的cookie
- PersistentTokenBasedRememberMeServices:清除remeberMe服务中用户的持久化token
- CompositeLogoutHandler:复合类型handler,可以组合执行其他多个LogoutHandler的逻辑
下面我们分析几个常用的LogoutHandler
。
CookieClearingLogoutHandler
它的logout
方法如下所示:
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
// 遍历每个cookie
// 执行apply逻辑,调用function
this.cookiesToClear.forEach((f) -> response.addCookie(f.apply(request)));
}
这个调用function的逻辑位于构造函数中:
public CookieClearingLogoutHandler(String... cookiesToClear) {
Assert.notNull(cookiesToClear, "List of cookies cannot be null");
List<Function<HttpServletRequest, Cookie>> cookieList = new ArrayList<>();
for (String cookieName : cookiesToClear) {
// 这里是function的逻辑
cookieList.add((request) -> {
Cookie cookie = new Cookie(cookieName, null);
String cookiePath = request.getContextPath() + "/";
cookie.setPath(cookiePath);
// 设置这些cookie的maxAge为0,即使cookie立即实效
cookie.setMaxAge(0);
cookie.setSecure(request.isSecure());
return cookie;
});
}
this.cookiesToClear = cookieList;
}
public CookieClearingLogoutHandler(Cookie... cookiesToClear) {
Assert.notNull(cookiesToClear, "List of cookies cannot be null");
List<Function<HttpServletRequest, Cookie>> cookieList = new ArrayList<>();
for (Cookie cookie : cookiesToClear) {
// 要求必须确保cookie的maxAge为0
Assert.isTrue(cookie.getMaxAge() == 0, "Cookie maxAge must be 0");
cookieList.add((request) -> cookie);
}
this.cookiesToClear = cookieList;
}
CsrfLogoutHandler
logout
方法:
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
// 使用csrfTokenRepository保存一个为null的token,即清空token
this.csrfTokenRepository.saveToken(null, request, response);
}
SecurityContextLogoutHandler
logout
方法逻辑如下所示:
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
Assert.notNull(request, "HttpServletRequest required");
// 如果需要使session失效
if (this.invalidateHttpSession) {
HttpSession session = request.getSession(false);
if (session != null) {
// 失效session
session.invalidate();
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Invalidated session %s", session.getId()));
}
}
}
if (this.clearAuthentication) {
SecurityContext context = SecurityContextHolder.getContext();
// 设置SecurityContext的用户认证信息为null
context.setAuthentication(null);
}
// 清除上下文对象
SecurityContextHolder.clearContext();
}
BasicAuthenticationFilter
支持使用HTTP basic方式认证。简单来说basic认证就是将用户名和密码附在HTTP请求头,如下所示:
Authorization: Basic XXXXXXX
其中XXXXXXX
是如下内容:
Base64(username:password)
我们从源代码开始分析下处理流程。
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
final boolean debug = this.logger.isDebugEnabled();
try {
// 解析http请求头的Authorization部分,使用base64解码,读取用户名和密码,创建UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken authRequest = authenticationConverter.convert(request);
// 如果请求头不包含basic认证内容,跳过此filter
if (authRequest == null) {
chain.doFilter(request, response);
return;
}
String username = authRequest.getName();
if (debug) {
this.logger
.debug("Basic Authentication Authorization header found for user '"
+ username + "'");
}
if (authenticationIsRequired(username)) {
// 调用AuthenticationManager的认证逻辑
Authentication authResult = this.authenticationManager
.authenticate(authRequest);
if (debug) {
this.logger.debug("Authentication success: " + authResult);
}
// 认证成功,设置securityContext
SecurityContextHolder.getContext().setAuthentication(authResult);
// 调用remember me服务的登陆成功方法(设置cookie,持久化记录用户登录状态)
this.rememberMeServices.loginSuccess(request, response, authResult);
// 调用认证成功回调函数
onSuccessfulAuthentication(request, response, authResult);
}
}
catch (AuthenticationException failed) {
// 认证失败的逻辑
SecurityContextHolder.clearContext();
if (debug) {
this.logger.debug("Authentication request for failed!", failed);
}
this.rememberMeServices.loginFail(request, response);
onUnsuccessfulAuthentication(request, response, failed);
if (this.ignoreFailure) {
chain.doFilter(request, response);
}
else {
this.authenticationEntryPoint.commence(request, response, failed);
}
return;
}
chain.doFilter(request, response);
}