SpringBoot程序员Java

Spring Security 源码之 LogoutFilter

2020-11-18  本文已影响0人  AlienPaul

LogoutFilter

负责处理用户登出操作。内部包含LogoutHandlerLogoutSuccessHandler。其中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的子类如下:

下面我们分析几个常用的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);
}
上一篇 下一篇

猜你喜欢

热点阅读