SecurityContextPersistenceFilter详解
试想一下,如果我们不使用Spring Security,如果保存用户信息呢,大多数情况下会考虑使用Session对吧?在Spring Security中也是如此,用户在登录过一次之后,后续的访问便是通过sessionId来识别,从而认为用户已经被认证。SecurityContextHolder存放用户信息,认证相关的信息是如何被存放到其中的,便是通过SecurityContextPersistenceFilter。SecurityContextPersistenceFilter的两个主要作用便是请求来临时,创建SecurityContext安全上下文信息和请求结束时清空SecurityContextHolder。微服务的一个设计理念需要实现服务通信的无状态,而http协议中的无状态意味着不允许存在session,这可以通过setAllowSessionCreation(false) 实现,这并不意味着SecurityContextPersistenceFilter变得无用,因为它还需要负责清除用户信息。在Spring Security中,虽然安全上下文信息被存储于Session中,但我们在实际使用中不应该直接操作Session,而应当使用SecurityContextHolder。
源码分析
org.springframework.security.web.context.SecurityContextPersistenceFilterpublic class SecurityContextPersistenceFilter extends GenericFilterBean {static final String FILTER_APPLIED = "__spring_security_scpf_applied";//安全上下文存储的仓库private SecurityContextRepository repo;public SecurityContextPersistenceFilter() {//HttpSessionSecurityContextRepository是SecurityContextRepository接口的一个实现类//使用HttpSession来存储SecurityContextthis(new HttpSessionSecurityContextRepository());}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 requestchain.doFilter(request, response);return;}request.setAttribute(FILTER_APPLIED, Boolean.TRUE);//包装request,responseHttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,response);//从Session中获取安全上下文信息SecurityContext contextBeforeChainExecution = repo.loadContext(holder);try {//请求开始时,设置安全上下文信息,这样就避免了用户直接从Session中获取安全上下文信息SecurityContextHolder.setContext(contextBeforeChainExecution);chain.doFilter(holder.getRequest(), holder.getResponse());}finally {//请求结束后,清空安全上下文信息SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();SecurityContextHolder.clearContext();repo.saveContext(contextAfterChainExecution, holder.getRequest(),holder.getResponse());request.removeAttribute(FILTER_APPLIED);if (debug) {logger.debug("SecurityContextHolder now cleared, as request processing completed");}}}}
过滤器一般负责核心的处理流程,而具体的业务实现,通常交给其中聚合的其他实体类,这在Filter的设计中很常见,同时也符合职责分离模式。例如存储安全上下文和读取安全上下文的工作完全委托给了HttpSessionSecurityContextRepository去处理,而这个类中也有几个方法可以稍微解读下,方便我们理解内部的工作流程
org.springframework.security.web.context.HttpSessionSecurityContextRepositorypublic class HttpSessionSecurityContextRepository implements SecurityContextRepository {// 'SPRING_SECURITY_CONTEXT'是安全上下文默认存储在Session中的键值public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";...private final Object contextObject = SecurityContextHolder.createEmptyContext();private boolean allowSessionCreation = true;private boolean disableUrlRewriting = false;private String springSecurityContextKey = SPRING_SECURITY_CONTEXT_KEY;private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();//从当前request中取出安全上下文,如果session为空,则会返回一个新的安全上下文public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {HttpServletRequest request = requestResponseHolder.getRequest();HttpServletResponse response = requestResponseHolder.getResponse();HttpSession httpSession = request.getSession(false);SecurityContext context = readSecurityContextFromSession(httpSession);if (context == null) {context = generateNewContext();}...return context;}...public boolean containsContext(HttpServletRequest request) {HttpSession session = request.getSession(false);if (session == null) {return false;}return session.getAttribute(springSecurityContextKey) != null;}private SecurityContext readSecurityContextFromSession(HttpSession httpSession) {if (httpSession == null) {return null;}...// Session存在的情况下,尝试获取其中的SecurityContextObject contextFromSession = httpSession.getAttribute(springSecurityContextKey);if (contextFromSession == null) {return null;}...return (SecurityContext) contextFromSession;}//初次请求时创建一个新的SecurityContext实例protected SecurityContext generateNewContext() {return SecurityContextHolder.createEmptyContext();}}
SecurityContextPersistenceFilter和HttpSessionSecurityContextRepository配合使用,构成了Spring Security整个调用链路的入口,为什么将它放在最开始的地方也是显而易见的,后续的过滤器中大概率会依赖Session信息和安全上下文信息。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
