• SpringSecurity源码学习一:过滤器执行原理



    在Spring Security源代码中,过滤器是用于处理安全相关任务的关键组件之一。它们用于在请求到达应用程序之前或之后执行特定的安全操作。

    Spring Security中的过滤器链是一个由多个过滤器组成的链式结构。每个过滤器都负责执行特定的安全任务,并将请求传递给下一个过滤器。这些过滤器一起协同工作,以实现身份验证、授权、会话管理等安全功能。

    在Spring Security源码中,有一个关键的过滤器叫做 FilterChainProxy ,它是整个过滤器链的入口点。 FilterChainProxy 负责协调和执行所有其他过滤器,并确保请求按照正确的顺序通过它们。 也就是我们自定义的过滤器是通过 FilterChainProxy插入到web过滤器链中的

    在这里插入图片描述
    一般过滤器链路
    在这里插入图片描述

    使用Spring Security的过滤器链路

    1. web过滤器Filter

    Filter就是过滤器,在JavaWeb体系中,他位于服务端,卡在请求/响应与Servlet之间做一些操作。通过过滤器我们可以做很多操作,例如:

    1. 认证和授权:Spring Security框架中的过滤器可以用于对用户进行身份验证和授权,以保护Web应用程序的安全性。

    2. 日志记录:可以使用过滤器记录HTTP请求和响应的详细信息,以便进行故障排除和性能优化。

    3. 缓存:可以使用过滤器实现缓存机制,以提高Web应用程序的性能和响应速度。

    4. 压缩:可以使用过滤器对响应进行压缩,以减少数据传输量,提高Web应用程序的性能。

    5. 字符编码:可以使用过滤器对请求和响应进行字符编码,以确保正确的数据传输和显示。

    1.1 filter核心类

    在Spring Web中,实现过滤器的核心类是 javax.servlet.Filter 。这是Java Servlet API中定义的标准接口。要在Spring Web中创建过滤器,通常需要创建一个实现 Filter 接口的类,并重写其方法,例如 doFilter() 。
    以下是与Spring Web过滤器相关的一些核心类:

    1. OncePerRequestFilter :这是Spring提供的一个抽象类,扩展了 GenericFilterBean 。它确保每个请求只调用一次过滤器的 doFilter() 方法。
    2. GenericFilterBean :这是一个方便的基类,用于创建过滤器。它实现了 Filter 接口,并提供了额外的功能,如bean生命周期管理。
    3. DelegatingFilterProxy :这是一个Spring特定的类,它将过滤器功能委托给Spring应用程序上下文中定义的目标bean。通常在想要将过滤器应用于特定URL或向过滤器注入依赖项时使用。
      这些核心类有助于在Spring Web应用程序中创建和配置过滤器,允许您根据需求拦截和处理HTTP请求和响应。

    1.2 GenericFilterBean

    GenericFilterBean 是Spring Web框架提供的一个方便的基类,用于创建过滤器。它实现了 Filter 接口,并提供了一些额外的功能,例如bean生命周期管理和依赖注入。 GenericFilterBean 是自带init方法的,我们继承后只需要重写doFilter方法。
    直接继承 GenericFilterBean 可以带来以下好处:

    1. 简化过滤器的创建:继承 GenericFilterBean 可以减少编写过滤器所需的代码量。它提供了一些默认的实现和方法,使得创建过滤器变得简单和方便。

    2. 生命周期管理: GenericFilterBean 实现了 Filter 接口,并提供了生命周期管理的功能。它可以在过滤器的初始化和销毁过程中执行相应的操作,例如资源的初始化和释放。

    3. 依赖注入: GenericFilterBean 支持依赖注入,您可以使用 @Autowired 注解将其他Spring bean注入到过滤器中。这使得在过滤器中使用其他组件、服务或依赖变得更加容易。

    4. 配置灵活性:通过继承 GenericFilterBean ,您可以根据需要自定义和配置过滤器的行为。您可以重写 doFilter() 方法以实现自定义的请求和响应处理逻辑,并使用其他提供的方法来访问过滤器的配置和上下文信息。

    1.3 DelegatingFilterProxy

    上边我们讲了GenericFilterBean,我们可以通过继承GenericFilterBean编写过滤器。此时的自定义的过滤器是交由Spring容器管理的,并不属于web的Servlet 容器管理。因此可以将过滤器分类两类,第一类是由 Web 容器进行管理,第二类是由 Spring 进行管理的,DelegatingFilterProxy 就是连接二者的桥梁。

    1.3.1 原理

    DelegatingFilterProxy是Spring框架提供的一个过滤器代理类,它可以将请求转发到一个或多个过滤器。它的原理是将一个实现了javax.servlet.Filter接口的Bean转换为Servlet过滤器,并将这些过滤器应用到Web应用程序中。
    在Spring中,DelegatingFilterProxy通常与Spring的IoC容器结合使用。当一个请求到达Web应用程序时,Servlet容器会将请求传递给DelegatingFilterProxy。DelegatingFilterProxy会从Spring的IoC容器中获取目标过滤器的实例,并调用它的doFilter方法来处理请求。

    1.3.2 DelegatingFilterProxy源码

    	@Override
    	public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
    			throws ServletException, IOException {
    
    		// Lazily initialize the delegate if necessary.
    		//这个时候经过初始化,filter已经是spring容器中的bean,在spring-Security中,此时delegateToUse就是spring-Security的FilterChainProxy
    		Filter delegateToUse = this.delegate;
    		if (delegateToUse == null) {
    			synchronized (this.delegateMonitor) {
    				delegateToUse = this.delegate;
    				if (delegateToUse == null) {
    					WebApplicationContext wac = findWebApplicationContext();
    					if (wac == null) {
    						throw new IllegalStateException("No WebApplicationContext found: " +
    								"no ContextLoaderListener or DispatcherServlet registered?");
    					}
    					delegateToUse = initDelegate(wac);
    				}
    				this.delegate = delegateToUse;
    			}
    		}
    
    		// Let the delegate perform the actual doFilter operation.
    		//代理模式 让代理Filter执行实际的doFilter方法,在spring-Security中,此时就是执行spring-Security的FilterChainProxy
    		invokeDelegate(delegateToUse, request, response, filterChain);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在源码中我们可以看到,使用了代理模式执行了被代理的过滤器的过滤方法。

    2. FilterChainProxy源码学习

    在Spring Security中,FilterChainProxy是一个核心的过滤器,它负责处理Web请求的安全过滤链。它的作用是将请求传递给一系列的安全过滤器,以便进行身份验证、授权、会话管理等安全相关的操作。

    FilterChainProxy的主要功能是根据配置的安全过滤器链来处理请求。在Spring Security的配置中,可以定义多个过滤器,并指定它们的顺序和应用于特定URL模式的条件。当一个请求到达应用程序时,FilterChainProxy会按照配置的顺序依次调用这些过滤器,直到找到适用于请求的过滤器链。

    每个过滤器链都包含一系列的过滤器,它们按照特定的顺序执行。这些过滤器可以执行不同的安全操作,例如身份验证过滤器用于验证用户的身份,授权过滤器用于检查用户是否具有访问权限,会话管理过滤器用于管理用户的会话等。

    通过FilterChainProxy,Spring Security提供了一种灵活的方式来定义和管理安全过滤器链。它可以根据应用程序的需求进行配置,并提供了强大的安全功能,以保护Web应用程序免受各种安全威胁。

    2.1 源码

    public class FilterChainProxy extends GenericFilterBean {
    
    • 1

    可以看到,FilterChainProxy是继承了GenericFilterBean,FilterChainProxy 也是一个过滤器。

    	@Override
    	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
    		//此处是对请求是否第一次经过这个过滤器的判断,在spring security中的过滤器中这种用法是相当常见的
    		boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
    		if (!clearContext) {
    			doFilterInternal(request, response, chain);
    			return;
    		}
    		try {
    			request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
    			doFilterInternal(request, response, chain);
    		}
    		catch (RequestRejectedException ex) {
    			this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, ex);
    		}
    		finally {
    			//清除security上下文中的保存的Authentication对象
    			SecurityContextHolder.clearContext();
    			request.removeAttribute(FILTER_APPLIED);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    这是FilterChainProxy的大概逻辑,细节点在doFilterInternal方法中。

    2.1.1 doFilterInternal方法源码

    	private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
    		//初始化request,对request进行一层包装,同时校验了一下请求的方式是否允许,以及请求地址中是否有非法字符
    		FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
    		//初始化response,对response进行了一层包装,若在response中添加header或cookie时,会对header和cookie的一些属性进行校验
    		HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
    		//SpringSecurity一般都会注册很多默认Filter,
    		//注意这些Filter在容器启动时就初始化好了
    		//获取spring security中第一个匹配请求地址的过滤器列表
    		List<Filter> filters = getFilters(firewallRequest);
    		if (filters == null || filters.size() == 0) {
    			if (logger.isTraceEnabled()) {
    				logger.trace(LogMessage.of(() -> "No security for " + requestLine(firewallRequest)));
    			}
    			firewallRequest.reset();
    			chain.doFilter(firewallRequest, firewallResponse);
    			return;
    		}
    		if (logger.isDebugEnabled()) {
    			logger.debug(LogMessage.of(() -> "Securing " + requestLine(firewallRequest)));
    		}
    		//VirtualFilterChain是一个过滤器链FilterChain
    		//将过滤器列表和servlet过滤器链包装成一个新的过滤器链 VirtualFilterChain 是FilterChainProxy的内部类
    		VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
    		virtualFilterChain.doFilter(firewallRequest, firewallResponse);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    这段主要功能是:

    1. getFilters()方法,获取spring security中第一个匹配请求地址的过滤器列表
    2. 创建VirtualFilterChain过滤器链,执行所有过滤器。
    2.1.1.1 getFilters()方法源码
    	/**
    	 * Returns the first filter chain matching the supplied URL.
    	 * @param request the request to match
    	 * @return an ordered array of Filters defining the filter chain
    	 */
    	private List<Filter> getFilters(HttpServletRequest request) {
    		int count = 0;
    		for (org.springframework.security.web.SecurityFilterChain chain : this.filterChains) {
    			if (logger.isTraceEnabled()) {
    				logger.trace(LogMessage.format("Trying to match request against %s (%d/%d)", chain, ++count,
    						this.filterChains.size()));
    			}
    			if (chain.matches(request)) {
    				return chain.getFilters();
    			}
    		}
    		return null;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    根据请求地址匹配过滤器列表

    2.1.1.2 VirtualFilterChain方法源码
    		//VirtualFilterChain是一个过滤器链FilterChain
    		//将过滤器列表和servlet过滤器链包装成一个新的过滤器链 VirtualFilterChain 是FilterChainProxy的内部类
    		VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
    		virtualFilterChain.doFilter(firewallRequest, firewallResponse);
    
    • 1
    • 2
    • 3
    • 4
    	/**
    	 * Internal {@code FilterChain} implementation that is used to pass a request through
    	 * the additional internal list of filters which match the request.
    	 */
    	private static final class VirtualFilterChain implements FilterChain {
    
    		private final FilterChain originalChain;
    
    		private final List<Filter> additionalFilters;
    
    		private final FirewalledRequest firewalledRequest;
    
    		private final int size;
    
    		private int currentPosition = 0;
    
    		private VirtualFilterChain(FirewalledRequest firewalledRequest, FilterChain chain,
    				List<Filter> additionalFilters) {
    			this.originalChain = chain;
    			this.additionalFilters = additionalFilters;
    			this.size = additionalFilters.size();
    			this.firewalledRequest = firewalledRequest;
    		}
    
    		@Override
    		public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
    			//如果没有下一个过滤器,则回到servlet过滤器链
    			if (this.currentPosition == this.size) {
    				if (logger.isDebugEnabled()) {
    					logger.debug(LogMessage.of(() -> "Secured " + requestLine(this.firewalledRequest)));
    				}
    				// Deactivate path stripping as we exit the security filter chain
    				this.firewalledRequest.reset();
    				this.originalChain.doFilter(request, response);
    				return;
    			}
    			this.currentPosition++;
    			Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
    			if (logger.isTraceEnabled()) {
    				logger.trace(LogMessage.format("Invoking %s (%d/%d)", nextFilter.getClass().getSimpleName(),
    						this.currentPosition, this.size));
    			}
    			//传入this,nextFilter中的会继续调用VirtualFilterChain.doFilter()直至所有过滤器执行结束或抛出异常
    			nextFilter.doFilter(request, response, this);
    		}
    
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    VirtualFilterChain一个过滤器链,执行additionalFilters集合中的所有过滤器。 也可以把VirtualFilterChain当作SecurityFilterChain,additionalFilters集合中的元素就是 Security Filter,也就是我们自己写的过滤器。

    3. 总结

    在这里插入图片描述

    在 上图中,一个请求进来会进入web容器中,从上到下按顺序执行过滤器。当执行到DelegatingFilterProxy过滤器中,会代理到FilterChainProxy类中。FilterChainProxy 决定应该使用哪个 SecurityFilterChain。只有第一个匹配的 SecurityFilterChain 被调用。

    1. 如果请求的URL是 /api/messages/,它首先与 /api/** 的 SecurityFilterChain0 模式匹配,所以只有 SecurityFilterChain0 被调用,尽管它也与 SecurityFilterChainn 匹配。
    2. 如果请求的URL是 /messages/,它与 /api/** 的 SecurityFilterChain0 模式不匹配,所以 FilterChainProxy 继续尝试每个 SecurityFilterChain。假设没有其他 SecurityFilterChain 实例相匹配,则调用 SecurityFilterChainn。
  • 相关阅读:
    【springBoot】统一功能处理
    Ant Design Vue UI框架的基础使用,及通用后台管理模板的小demo【简单】
    【算法面试必刷Java版十三】判断一个链表是否为回文结构
    【2023春李宏毅机器学习】快速了解机器学习基本原理
    【C#】【winform】Microsoft Visual Studio Installer Project 打包应用程序全部过程
    频次直方图、KDE和密度图
    第二节:vuex与wabpack基本配置
    ChatGPT Prompting开发实战(八)
    HTML+CSS大作业 格林蛋糕(7个页面) 餐饮美食网页设计与实现
    基于Docker-compose构建Kafka集群
  • 原文地址:https://blog.csdn.net/qq_27586963/article/details/133658870