前言
今天在测试Spring security一些过滤器的功能,突然想到我们在security的过滤链中自定义认证过滤器,存放SecurityContext认证对象Authentication,以至于后续授权会根据我们自己的Authentication去实现.如果我们在Springboot里创建一个单纯的Filter,把SecurityContext设置认证对象Authentication,是否会在spring security中同样生效呢? 经过验证,不可以.
验证之后有点想不通,SecurityContext底层TreadLocal存储的,同一个访问应该都能拿到Authentication为什么不可以使用呢.
SecurityContextPersistentFilter
对于spring security有了解的朋友可能知道这个过滤器,他是管理SecurityContext生命周期的一个过滤器,也是在进入Spring security过滤链中比较靠前执行的过滤器.我们来观察这一段代码:
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
// ensure that filter is only applied once per request
if (request.getAttribute(FILTER_APPLIED) != null) {
chain.doFilter(request, response);
return;
}
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
if (this.forceEagerSessionCreation) {
HttpSession session = request.getSession();
if (this.logger.isDebugEnabled() && session.isNew()) {
this.logger.debug(LogMessage.format("Created session %s eagerly", session.getId()));
}
}
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
try {
SecurityContextHolder.setContext(contextBeforeChainExecution);
if (contextBeforeChainExecution.getAuthentication() == null) {
logger.debug("Set SecurityContextHolder to empty SecurityContext");
}
else {
if (this.logger.isDebugEnabled()) {
this.logger
.debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution));
}
}
chain.doFilter(holder.getRequest(), holder.getResponse());
}
finally {
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
// Crucial removal of SecurityContextHolder contents before anything else.
SecurityContextHolder.clearContext();
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
this.logger.debug("Cleared SecurityContextHolder to complete request");
}
}
其中有一行代码
SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
这段代码的意思就是,无论我们前面servlet filter 是否在SecurityContext中存放了认证信息,在SpringSecurity过滤器链开始之前,都要从这个repo(HttpSessionSecurityContextRepository是默认实现)获取一个context对象,重新放到SecurityContext,这样做就是把你的ServletFilter里的context清空了,然后才继续进入security其他过滤器中.
最后finally又有代码
finally {
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
// Crucial removal of SecurityContextHolder contents before anything else.
SecurityContextHolder.clearContext();
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
this.logger.debug("Cleared SecurityContextHolder to complete request");
}
这段代码又表示,当security的所有过滤器执行结束之后,清空SecurityContext上下文信息,同时,如果上下文有认证信息,存储到repo里,供后续使用,所以如果是session存储,只要会话存在,下一次访问不需要走认证流程依然可以保持认证状态.
这两段代码是关键.