认证信息的存储和会话跟踪
UsernamePasswordAuthenticationFilter认证只会拦截登录请求,上下文的创建和销毁是由SecurityContextPersistenceFilter来完成的。request请求经过SecurityContextPersistenceFilter时,SecurityContextPersistenceFilter会向SecurityContextHolder里存放上下文,SecurityContextPersistenceFilter的上下文取自上下文仓库,如果上下文仓库中没有当前用户上下文,则创建新的上下文;当response时,SecurityContextPersistenceFilter会从SecurityContextHolder中取出上下文放入上下文仓库,然后清理掉SecurityContextHolder中的上下文。如此就完成了会话跟踪,源码如下
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (request.getAttribute(FILTER_APPLIED) != null) {
// ensure that filter is only applied once per request
chain.doFilter(request, response);
return;
}
final boolean debug = logger.isDebugEnabled();
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
if (forceEagerSessionCreation) {
HttpSession session = request.getSession();
if (debug && session.isNew()) {
logger.debug("Eagerly created session: " + session.getId());
}
}
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
response);
// 请求前,从上下文仓库中加载上下文存放在SecurityContextHolder中
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
try {
SecurityContextHolder.setContext(contextBeforeChainExecution);
chain.doFilter(holder.getRequest(), holder.getResponse());
}
finally {
//请求返回时,从SecurityContextHolder中取出上下文存放在上下文仓库
SecurityContext contextAfterChainExecution = SecurityContextHolder
.getContext();
// Crucial removal of SecurityContextHolder contents - do this before anything
// else.
SecurityContextHolder.clearContext();
repo.saveContext(contextAfterChainExecution, holder.getRequest(),
holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
if (debug) {
logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
SecurityContextRepository中的上下文存储
SecurityContextRepository的上下文存储在session中,用SPRING_SECURITY_CONTEXT做键,如下
private SecurityContext readSecurityContextFromSession(HttpSession httpSession) {
final boolean debug = logger.isDebugEnabled();
if (httpSession == null) {
if (debug) {
logger.debug("No HttpSession currently exists");
}
return null;
}
// Session exists, so try to obtain a context from it.
Object contextFromSession = httpSession.getAttribute(springSecurityContextKey);
if (contextFromSession == null) {
if (debug) {
logger.debug("HttpSession returned null object for SPRING_SECURITY_CONTEXT");
}
return null;
}
// We now have the security context object from the session.
if (!(contextFromSession instanceof SecurityContext)) {
if (logger.isWarnEnabled()) {
logger.warn(springSecurityContextKey
+ " did not contain a SecurityContext but contained: '"
+ contextFromSession
+ "'; are you improperly modifying the HttpSession directly "
+ "(you should always use SecurityContextHolder) or using the HttpSession attribute "
+ "reserved for this class?");
}
return null;
}
if (debug) {
logger.debug("Obtained a valid SecurityContext from "
+ springSecurityContextKey + ": '" + contextFromSession + "'");
}
// Everything OK. The only non-null return from this method.
return (SecurityContext) contextFromSession;
}