注:Springsecurity版本4.3.x.RELEASE
先上一张LogoutFilter的类继承图,如下图1所示,原图见我的Github。
图1
LogoutFilter和其它Springsecurity的Filter一样,都是继承自GenericFilterBean。
来看下LogoutFilter的属性和构造方法,如下List-1所示。当我们定义了如List-2所示的bean时,调用的是List-1中的第二个构造方法。
List-1
public class LogoutFilter extends GenericFilterBean {
private RequestMatcher logoutRequestMatcher;
private final LogoutHandler handler;
private final LogoutSuccessHandler logoutSuccessHandler;
/**
* Constructor which takes a <tt>LogoutSuccessHandler</tt> instance to determine the
* target destination after logging out. The list of <tt>LogoutHandler</tt>s are
* intended to perform the actual logout functionality (such as clearing the security
* context, invalidating the session, etc.).
*/
public LogoutFilter(LogoutSuccessHandler logoutSuccessHandler,
LogoutHandler... handlers) {
this.handler = new CompositeLogoutHandler(handlers);
Assert.notNull(logoutSuccessHandler, "logoutSuccessHandler cannot be null");
this.logoutSuccessHandler = logoutSuccessHandler;
setFilterProcessesUrl("/logout");
}
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");
}
List-2
<bean id="requestSingleLogoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
<constructor-arg value="https://localhost:9443/cas/logout"/>
<constructor-arg>
<bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
</constructor-arg>
<property name="filterProcessesUrl" value="/logout/cas"/>
</bean>
来看下LogoutFilter的doFilter方法,如下List-3所示,
List-3
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.onLogoutSuccess(request, response, auth);
return;
}
chain.doFilter(request, response);
}
在List-3中:
- requiresLogout方法判断requst中的url是否是/logout/cas,见List-2中的属性filterProcessesUrl
- 如果满足步骤1的要求,那么调用LogoutHandler的logout方法,会将Session失效,此外将SecurityContextHolder清空
- 如果满足步骤1的要求,那么调用LogoutSuccessHandler的onLogoutSuccess方法,设置HttpServletResponse的重定向
- 如果满足步骤1的要求,那么不会调用FilterChain了
来看下SecurityContextLogoutHandler的logout方法,如下List-4所示,
List-4
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
Assert.notNull(request, "HttpServletRequest required");
if (invalidateHttpSession) {
HttpSession session = request.getSession(false);
if (session != null) {
logger.debug("Invalidating session: " + session.getId());
session.invalidate();
}
}
if (clearAuthentication) {
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(null);
}
SecurityContextHolder.clearContext();
}
在List-4中:
- 会将HttpSession失效
- 清空SecurityContextHolder的context
来看下SimpleUrlLogoutSuccessHandler,如图2和List-5所示,它直接调用父类AbstractAuthenticationTargetUrlRequestHandler的handle方法,看List-6所示,方法determineTargetUrl决定使用哪个targetUrl;List-6中handle方法的redirectStrategy.sendRedirect(request, response, targetUrl),调用的DefaultRedirectStrategy的sendRedirect方法,如List-7所示,最终调用的是response的sendRedirect方法。
图2
List-5
public class SimpleUrlLogoutSuccessHandler extends
AbstractAuthenticationTargetUrlRequestHandler implements LogoutSuccessHandler {
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
super.handle(request, response, authentication);
}
}
List-6
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);
}
/**
* Builds the target URL according to the logic defined in the main class Javadoc.
*/
protected String determineTargetUrl(HttpServletRequest request,
HttpServletResponse response) {
if (isAlwaysUseDefaultTargetUrl()) {
return defaultTargetUrl;
}
// Check for the parameter and use that if available
String targetUrl = null;
if (targetUrlParameter != null) {
targetUrl = request.getParameter(targetUrlParameter);
if (StringUtils.hasText(targetUrl)) {
logger.debug("Found targetUrlParameter in request: " + targetUrl);
return targetUrl;
}
}
if (useReferer && !StringUtils.hasLength(targetUrl)) {
targetUrl = request.getHeader("Referer");
logger.debug("Using Referer header: " + targetUrl);
}
if (!StringUtils.hasText(targetUrl)) {
targetUrl = defaultTargetUrl;
logger.debug("Using default Url: " + targetUrl);
}
return targetUrl;
}
List-7
public void sendRedirect(HttpServletRequest request, HttpServletResponse response,
String url) throws IOException {
String redirectUrl = calculateRedirectUrl(request.getContextPath(), url);
redirectUrl = response.encodeRedirectURL(redirectUrl);
if (logger.isDebugEnabled()) {
logger.debug("Redirecting to '" + redirectUrl + "'");
}
response.sendRedirect(redirectUrl);
}