【JavaWeb】SpringSecurity过滤链的理解

写在最前,本人也只是个大三的学生,如果你发现任何我写的不对的,请在评论中指出。

Spring Security过滤链

如何查看整条过滤链?

  根据spring官网的说辞,为了能更好的了解spring security这个框架,官方更推荐我们从spring security的过滤链开始研究,所以我直接从官网扒了一张图开始研究:
在这里插入图片描述

  首先,spring容器与servlet容器是两个不相关的概念,但spring security的servlet为了支持基于servlet的过滤器,因此设计了DelegatingFilterProxy,可以在servlet容器的生命周期和spring的ApplicationContext之间进行桥接。而图中的FilterChainProxy则是spring security提供的用于获取过滤链的bean, 它可以被DelegatingFilterProxyApplicationContext中获取。
  那么接下来我们可以直接启动一个spring security demo来根据官方给出的几个代理类来查看主要流程,实际上我们可以直接在FilterChainProxy的doFilter方法处打上断点,对于DelegatingFilterProxy可以直接关注它的invokeDelegate方法(这个方法才是真正的dofilter逻辑, 该方法前面的都是进行初始化)。

继续查看doFilter方法
在这里插入图片描述
再进入doFilterInternal方法,就可以浏览当前spring security的过滤链
在这里插入图片描述

继续跟进FilterChainProxy, 它还是实现了一个内部类VirtualFilterChain ,可以让过滤链集合中的过滤器工作
在这里插入图片描述
  到这里,基本就是过滤器链开始执行各过滤器的核心过滤器方法,之后就是spring security认证通过。

过滤链介绍

  记录一下目前编写的demo在addtionalFiliters内的出现的部分filters。但在这之前需要提一下OncePerRequestFilter,spring security部分的过滤器都继承了该类,该类的意旨在于每个请求仅执行一次(以解决各种容器不兼容的问题),其中doFilterInternal留白给子类发挥空间。

  • SecurityContextPersistenceFilter

  该过滤类,在源码介绍的最前就摆明了自身的功能:可以从之前配置的SecurityContextRepository中取得已经存储了信息的SecurityContextHodler,并在请求完成之后清除原先的SecurityContextHodler,将新的SecurityContextHodler存储回SecurityContextHodler,以供后续的过滤器使用。

// 直接看到dofilter方法内
// HttpRequestResponseHolder仅仅只是把request和response封装到了一起
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
// loadContext方法会判断当前的SecurityContext是否空,为空则new一个,不为空则保存到SaveToSessionResponseWrapper,供后续使用
SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
// 之后就是将获取到的这个context通过set方法保留,然后根据之前context内的认证信息(null ? true : false)日志记录
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());
}
// 之后就是开头所说,需要清理。请求结束后清除SecurityContextHolder中的SecurityContext的操作是必须的
// 因为默认情况下SecurityContextHolder会把SecurityContext存储到ThreadLocal中,而这个thread刚好是存在于servlet容器的线程池中的
// 如果不清除,当后续请求又从线程池中分到这个线程时,程序就会拿到错误的认证信息
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");
}
  • HeaderWriterFilter

  这个过滤类的话,源码也不多,文档解析也很清除明了,Spring Securty 使用该Filter在一个请求的处理过程中为响应对象增加一些头部信息。头部信息由外部提供,比如用于增加一些浏览器保护的头部,比如X-Frame-Options, X-XSS-Protection和X-Content-Type-Options等。

  • LogoutFilter

  登出过滤器,会轮询一系列的LogoutHandler,这些处理器是注销的时候而调用。

// 直接上源码
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
	// 判读是否需要退出,主要通过请求的url是否是filterProcessesUrl来识别
	if (requiresLogout(request, response)) {
		// 获取认证实例
		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
		if (this.logger.isDebugEnabled()) {
			this.logger.debug(LogMessage.format("Logging out [%s]", auth));
		}
		this.handler.logout(request, response, auth);
		// 退出成功后,进行redirect操作
		this.logoutSuccessHandler.onLogoutSuccess(request, response, auth);
		return;
	}
	chain.doFilter(request, response);
}

  一般而言,我们在SecurityContextPersistenceFilter中只产生了一个空的SecurityContext,这里应该是一个没有认证信息的SecurityContext实例,得到的应该是Null吧?但注意这个是登出过滤器, 这条分支走到是登出时的信息, 所以此时存在实例信息的。需要注意的是后续的注销处理器, 至少有一个SecurityContextLogoutHandler,如果有记住我这个服务,就还有一个处理器,不过它们的任务基本上都是:让session失效和清除SecurityContext实例

  • RequestCacheAwareFilter

  该过滤链在源码中的介绍是一个已缓存的请求与当前请求匹配,则负责重构保存的请求,白话讲就是:用于用户登录成功后,重新恢复因为登录被打断的请求,就不多讲了(我不会)。

  • SecurityContextHolderAwareRequestFilter

  再一次看到源码介绍,简单粗暴的讲了该过滤器是用于包装ServletRequest的。

// 源码看起来也异常的简单
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
		throws IOException, ServletException {
	chain.doFilter(this.requestFactory.create((HttpServletRequest) req, (HttpServletResponse) res), res);
}

  这个过滤器看起来很简单。目的仅仅是实现java ee中servlet api一些接口方法。 一些应用中直接使用getRemoteUser方法、isUserInRole方法,在使用spring security时其实就是通过这个过滤器来实现的。

  • AnonymousAuthenticationFilter

  该过滤器理解起来也比较容易,因为很容易会在security config配置类里配置一些允许匿名访问的路径。实际上就是通过该过滤器实现的。它会手工设置了一个匿名的登录人Authentication。我们可以在其后的过滤器中,如果没有登录时,我们也可以知道当前访问的是匿名用户。

protected Authentication createAuthentication(HttpServletRequest request) {
	AnonymousAuthenticationToken token = new AnonymousAuthenticationToken(this.key, this.principal,
			this.authorities);
	token.setDetails(this.authenticationDetailsSource.buildDetails(request));
	return token;
}
  • SessionManagementFilter

  如果只看源码的话,它在dofilter方法中从SecurityContext中获取认证信息,并判断当前用户不是匿名用户,就将当前用户信息保存,并且通过securityContextRepository保存当前SecurityContext(想更详细的了解,可以通过下面两个链接):

SessionManagementFilter讲解上篇
SessionManagementFilter讲解下篇

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值