概述
Spring Security Web
认证机制(通常指表单登录)中登录成功后页面需要跳转到原来客户请求的URL。该过程中首先需要将原来的客户请求缓存下来,然后登录成功后将缓存的请求从缓存中提取出来。
针对该需求,Spring Security Web
提供了在http session
中缓存请求的能力,也就是HttpSessionRequestCache
。HttpSessionRequestCache
所保存的请求必须封装成一个SavedRequest
接口对象,实际上,HttpSessionRequestCache
总是使用自己的SavedRequest
缺省实现DefaultSavedRequest
。
源代码解析
package org.springframework.security.web.savedrequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.web.PortResolver;
import org.springframework.security.web.PortResolverImpl;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
/** * RequestCache which stores the SavedRequest in the HttpSession. * 将SavedRequest保存到HttpSession中的RequestCache。 * * The DefaultSavedRequest class is used as the implementation. * 这里使用的SavedRequest是其缺省实现DefaultSavedRequest。 * * @author Luke Taylor * @author Eddú Meléndez * @since 3.0 */
public class HttpSessionRequestCache implements RequestCache {
// 将请求缓存到session时缺省使用的session属性名称
static final String SAVED_REQUEST = "SPRING_SECURITY_SAVED_REQUEST";
protected final Log logger = LogFactory.getLog(this.getClass());
// 用于解析请求中的 server:port 信息
private PortResolver portResolver = new PortResolverImpl();
// 如果session不存在是否允许创建,缺省为true可以创建
private boolean createSessionAllowed = true;
// 用于判断哪些请求可以被缓存的请求匹配器,缺省为任何请求都可以被缓存,
// 实际上会被外部指定覆盖成:
// 1. 必须是 GET /**
// 2. 并且不能是 /**/favicon.*
// 3. 并且不能是 application.json
// 4. 并且不能是 XMLHttpRequest (也就是一般意义上的 ajax 请求)
private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;
// 将请求缓存到session时使用的session属性名称,初始化为使用SAVED_REQUEST
private String sessionAttrName = SAVED_REQUEST;
/** * Stores the current request, provided the configuration properties allow it. * 在配置属性requestMatcher匹配的情况下缓存当前请求 */
public void saveRequest(HttpServletRequest request, HttpServletResponse response) {
if (requestMatcher.matches(request)) {
// 在配置属性requestMatcher匹配的情况下缓存当前请求,
// 首先将当前请求包装成一个DefaultSavedRequest,也就是从当前请求中获取
// 各种必要的信息组装成一个DefaultSavedRequest
DefaultSavedRequest savedRequest = new DefaultSavedRequest(request,
portResolver);
// 获取session并执行缓存动作,也就是将上面创建的DefaultSavedRequest对象
// 添加为session的一个名称为this.sessionAttrName的属性
if (createSessionAllowed || request.getSession(false) != null) {
// Store the HTTP request itself. Used by
// AbstractAuthenticationProcessingFilter
// for redirection after successful authentication (SEC-29)
request.getSession().setAttribute(this.sessionAttrName, savedRequest);
logger.debug("DefaultSavedRequest added to Session: " + savedRequest);
}
}
else {
logger.debug("Request not saved as configured RequestMatcher did not match");
}
}
// 从session中提取所缓存的请求对象,也就是获取session中名称为this.sessionAttrName的属性,
// 如果 session 不存在直接返回 null
public SavedRequest getRequest(HttpServletRequest currentRequest,
HttpServletResponse response) {
HttpSession session = currentRequest.getSession(false);
if (session != null) {
return (SavedRequest) session.getAttribute(this.sessionAttrName);
}
return null;
}
// 从 session 中删除所缓存的请求对象,也就是移除session中名称为this.sessionAttrName的属性
public void removeRequest(HttpServletRequest currentRequest,
HttpServletResponse response) {
HttpSession session = currentRequest.getSession(false);
if (session != null) {
logger.debug("Removing DefaultSavedRequest from session if present");
session.removeAttribute(this.sessionAttrName);
}
}
// 从 session 获取缓存的请求对象,检验它和当前请求是否一致,如果一致的话将其封装成
// 一个SavedRequestAwareWrapper返回,同时删除所缓存的请求。其他情况则不做任何修改
// 动作,直接返回null。
public HttpServletRequest getMatchingRequest(HttpServletRequest request,
HttpServletResponse response) {
// 从 session 获取缓存的请求对象
SavedRequest saved = getRequest(request, response);
if (!matchesSavedRequest(request, saved)) {
// 如果缓存的请求和当前请求不匹配则返回null
logger.debug("saved request doesn't match");
return null;
}
// 如果缓存的请求和当前请求匹配则删除缓存中缓存的请求对象
removeRequest(request, response);
// 封装和返回从缓存中提取到的请求对象
return new SavedRequestAwareWrapper(saved, request);
}
// 检测当前请求和参数savedRequest是否匹配
private boolean matchesSavedRequest(HttpServletRequest request, SavedRequest savedRequest) {
if (savedRequest == null) {
return false;
}
if (savedRequest instanceof DefaultSavedRequest) {
// 如果savedRequest是一个DefaultSavedRequest,则使用DefaultSavedRequest的
// 方法doesRequestMatch检验是否匹配
DefaultSavedRequest defaultSavedRequest = (DefaultSavedRequest) savedRequest;
return defaultSavedRequest.doesRequestMatch(request, this.portResolver);
}
// 如果savedRequest不是一个DefaultSavedRequest,则通过比较二者的url是否相等
// 来检验二者是否匹配
String currentUrl = UrlUtils.buildFullRequestUrl(request);
return savedRequest.getRedirectUrl().equals(currentUrl);
}
/** * Allows selective use of saved requests for a subset of requests. By default any * request will be cached by the saveRequest method. * * If set, only matching requests will be cached. * * 指定哪些请求会被缓存,如果不指定,缺省情况是所有请求都会被缓存 * @param requestMatcher a request matching strategy which defines which requests * should be cached. */
public void setRequestMatcher(RequestMatcher requestMatcher) {
this.requestMatcher = requestMatcher;
}
/** * If true, indicates that it is permitted to store the target URL and * exception information in a new HttpSession (the default). In * situations where you do not wish to unnecessarily create HttpSessions * - because the user agent will know the failed URL, such as with BASIC or Digest * authentication - you may wish to set this property to false. */
public void setCreateSessionAllowed(boolean createSessionAllowed) {
this.createSessionAllowed = createSessionAllowed;
}
public void setPortResolver(PortResolver portResolver) {
this.portResolver = portResolver;
}
/** * If the sessionAttrName property is set, the request is stored in * the session using this attribute name. Default is * "SPRING_SECURITY_SAVED_REQUEST". * * @param sessionAttrName a new session attribute name. * @since 4.2.1 */
public void setSessionAttrName(String sessionAttrName) {
this.sessionAttrName = sessionAttrName;
}
}