Spring Security 过滤器注册脉络梳理

 更新时间:2022年08月11日 14:27:16   作者:青阳半雪  
这篇文章主要介绍了Spring Security过滤器注册脉络梳理,Spring Security在Servlet的过滤链中注册了一个过滤器FilterChainProxy,它会把请求代理到Spring Security自己维护的多个过滤链,每个过滤链会匹配一些URL,如果匹配则执行对应的过滤器

1 简述

Spring Security 本质上就是通过一系列的过滤器,进行业务的处理。

Spring Security 在 Servlet 的过滤链(filter chain)中注册了一个过滤器 FilterChainProxy,它会把请求代理到 Spring Security 自己维护的多个过滤链,每个过滤链会匹配一些 URL,如果匹配则执行对应的过滤器。过滤链是有顺序的,一个请求只会执行第一条匹配的过滤链。Spring Security 的配置本质上就是新增、删除、修改过滤器

但是万物终归有源头,过滤器是如何注册进来的,通过过程了解注册的骨架。

注明 这里只是使用了 spring web + spring security,同时使用 web.xml 的风格进行配置,如果你使用 SPI 机制(没有使用 web.xml),殊途同归。

2 注册过程

2.1 web.xml 配置

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

其中 DelegatingFilterProxy 类图如下:

根据类图看出,DelegatingFilterProxy 继承 GenericFilterBean,其间接实现了 Filter 接口,所以从类型上看,其也是一个过滤器。

 从 Spring 容器中寻找 targetBeanName=springSecurityFilterChain 的 Bean, 从 DelegatingFilterProxy 的名字上看,知道它其实是一个代理类,真正执行业务的,是 filter-name 指定的 springSecurityFilterChain 这个 bean。接下来,问题来了,springSecurityFilterChain 这个 bean 又是在什么时候注册的呢

2.2 EnableWebSecurity 注解

我们一般在使用 Spring Security 的时候,都会自定义继承 WebSecurityConfigurerAdapter,同时结合使用 EnableWebSecurity 注解,然后在自定义的类中进行各种各样满足业务的工作。

@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    ...
}

这里我们重点关注的是 EnableWebSecurity 注解,查看下其源码,做了两个非常重要的点

  • 1 导入 WebSecurityConfiguration 配置。
  • 2 通过 @EnableGlobalAuthentication 注解引入全局配置
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ 
    WebSecurityConfiguration.class,
    SpringWebMvcImportSelector.class,
    OAuth2ImportSelector.class,
    HttpSecurityConfiguration.class
})
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {

   /**
    * Controls debugging support for Spring Security. Default is false.
    * @return if true, enables debug support with Spring Security
    */
   boolean debug() default false;

}

此注解中由使用了 Import 注解引入了其他的 bean,然后查看其源码,重点关注 WebSecurityConfiguration

2.3 WebSecurityConfiguration 类

这个类里面比较重要的就两个方法:

1 springSecurityFilterChain

springSecurityFilterChain 方法上添加了 @Bean注解,可以知道是创建了springSecurityFilterChain bean

2 setFilterChainProxySecurityConfigurer

这个方式设置了对应的配置,注意,这个方法优先上面的方法执行。

分析其重点代码

private WebSecurity webSecurity;

// 注入 bean
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
   boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
   boolean hasFilterChain = !this.securityFilterChains.isEmpty();
   ...
   if (!hasConfigurers && !hasFilterChain) {
      WebSecurityConfigurerAdapter adapter = this.objectObjectPostProcessor
            .postProcess(new WebSecurityConfigurerAdapter() {
            });
      this.webSecurity.apply(adapter);
   }
   for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
      this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
      for (Filter filter : securityFilterChain.getFilters()) {
         if (filter instanceof FilterSecurityInterceptor) {
            this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
            break;
         }
      }
   }
   for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
      customizer.customize(this.webSecurity);
   }

   // 重点关注
   return this.webSecurity.build();
}
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> objectPostProcessor,
      @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
      throws Exception {
   this.webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
   if (this.debugEnabled != null) {
      this.webSecurity.debug(this.debugEnabled);
   }
   webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);
   Integer previousOrder = null;
   Object previousConfig = null;
   for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
      Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
      if (previousOrder != null && previousOrder.equals(order)) {
         throw new IllegalStateException("@Order on WebSecurityConfigurers must be unique. Order of " + order
               + " was already used on " + previousConfig + ", so it cannot be used on " + config + " too.");
      }
      previousOrder = order;
      previousConfig = config;
   }
   for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
      this.webSecurity.apply(webSecurityConfigurer);
   }
   this.webSecurityConfigurers = webSecurityConfigurers;
}

我们先看执行的 setFilterChainProxySecurityConfigurer 方法,其中参数 webSecurityConfigurers 是一个 List,它实际上是所有 WebSecurityConfigurerAdapter 的子类,那如果我们定义了自定义的配置类,也意味着读取了我们自定义的类。

接着看到 springSecurityFilterChain 方法注册了一个名字为 AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME 的 bean,翻看源码,其实名字就是在 web.xml 中配置的 filter-name,即 springSecurityFilterChain,到这里困惑解开了一部分,原来查询的 bean 是在这里注入的。同时也要求了在使用 spring-security 时,在 web.xml 中配置 filter 时,不能是其他名字。

根据 this.webSecurity.build 这行代码,发现真正构建过滤器的是 WebSecurity 类

2.4 WebSecurity 类

接上一步中的代码

   // 重点关注
   return this.webSecurity.build();

知道起作用的是 WebSecurity 类,其类图结构如下:

根据源码,定位到 WebSecurity 类中的 performBuild 方法

@Override
protected Filter performBuild() throws Exception {
   ...
   int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
   List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
   for (RequestMatcher ignoredRequest : this.ignoredRequests) {
      securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
   }

   // ① 调用 securityFilterChainBuilder 的 build() 方法
   for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
      securityFilterChains.add(securityFilterChainBuilder.build());
   }
   FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
   if (this.httpFirewall != null) {
      filterChainProxy.setFirewall(this.httpFirewall);
   }
   if (this.requestRejectedHandler != null) {
      filterChainProxy.setRequestRejectedHandler(this.requestRejectedHandler);
   }
   filterChainProxy.afterPropertiesSet();

   Filter result = filterChainProxy;
   ...
   this.postBuildAction.run();
   return result;
}

代码分析:

  • 1 代码中的  处,重点关注,通过断点调试,发现其实这里的 securityFilterChainBuilder 就是 HttpSecurity,到这里也很清楚了,默认以及自定义过滤器是通过 HttpSecurity 加载进来的。
  • 2 创建好过滤器链之后,然后实例化 filterChainProxy 进行返回

根据源码知道 performBuild 方法最终返回的其实是一个 filterChainProxy 实例,接下来我们再关注下 filterChainProxy 类。

2.5 FilterChainProxy 类

其类图结构如下

查看其关键代码:

public class FilterChainProxy extends GenericFilterBean {

   // 维护的 spring security 过滤器链列表
   private List<SecurityFilterChain> filterChains;
   public FilterChainProxy(SecurityFilterChain chain) {
      this(Arrays.asList(chain));
   }
   public FilterChainProxy(List<SecurityFilterChain> filterChains) {
      this.filterChains = filterChains;
   }
    @Override
    public void doFilter(
        ServletRequest request,
        ServletResponse response,
        FilterChain chain) throws IOException, ServletException {
            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);
            }
    ...
    }
    private void doFilterInternal(
        ServletRequest request,
        ServletResponse response,
        FilterChain chain) throws IOException, ServletException {
            FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest(
                (HttpServletRequest) request);
            HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse(
                (HttpServletResponse) response);
            List<Filter> filters = getFilters(firewallRequest);
            if (filters == null || filters.size() == 0) {
                    ...
                    chain.doFilter(firewallRequest, firewallResponse);
                    return;
            }
            VirtualFilterChain virtualFilterChain = new VirtualFilterChain(
                firewallRequest, chain, filters);
            virtualFilterChain.doFilter(firewallRequest, firewallResponse);
    }
}

通过代码分析,FilterChainProxy 这个类维护了真正的过滤器链列表,即 SecurityFilterChain 类型的过滤器链,注意,SecurityFilterChain 是过滤器链,而不是一个个的过滤器,过滤器会有其他的操作塞到过滤器链中,即 2.4 中的代码块,代码如下。

   for (RequestMatcher ignoredRequest : this.ignoredRequests) {
      securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
   }
   // ① 调用 securityFilterChainBuilder 的 build() 方法
   for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
      securityFilterChains.add(securityFilterChainBuilder.build());
   }

这段代码其实执行的业务就是把过滤器塞入到过滤器链中。

这里也更加清楚了, Spring Security Filter 并不是直接嵌入到 Web Filter 中的,而是通过 FilterChainProxy 来统一管理 Spring Security FilterFilterChainProxy 本身则通过 Spring 提供的 DelegatingFilterProxy 代理过滤器嵌入到 Web Filter 之中。

3 小结

根据趟源码,大概了解了过滤器注册的流程:

  • web.xml 中 DelegatingFilterProxy 配置
  • EnableWebSecurity 注解引入配置初始化
  • WebSecurityConfiguration 类注入 springSecurityFilterChain 的 bean 并开始构建过滤器
  • WebSecurity 类中的 performBuild 方法把过滤器都处理好,放到过滤器链中,接着实例化 filterChainProxy,这里实例化返回的对象就是第三步的过滤器

到此这篇关于Spring Security 过滤器注册脉络梳理的文章就介绍到这了,更多相关Spring Security 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java线程变量ThreadLocal详细解读

    Java线程变量ThreadLocal详细解读

    这篇文章主要介绍了Java线程变量ThreadLocal详细解读,多线程访问同一个变量的时候,很容易出现问题,特别是多线程对一个共享变量进行写入的时候,为了线程的安全在进行数据写入时候会进行数据的同步,需要的朋友可以参考下
    2024-01-01
  • Java中关于内存泄漏出现的原因汇总及如何避免内存泄漏(超详细版)

    Java中关于内存泄漏出现的原因汇总及如何避免内存泄漏(超详细版)

    这篇文章主要介绍了Java中关于内存泄漏出现的原因汇总及如何避免内存泄漏(超详细版)的相关资料,需要的朋友可以参考下
    2016-09-09
  • SSH框架网上商城项目第21战之详解易宝支付的流程

    SSH框架网上商城项目第21战之详解易宝支付的流程

    这篇文章主要为大家详细介绍了SSH框架网上商城项目第21战之易宝支付的流程,感兴趣的小伙伴们可以参考一下
    2016-06-06
  • Java  Object类中的常用API介绍

    Java  Object类中的常用API介绍

    这篇文章主要介绍了Java  Object类中的常用API介绍,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-11-11
  • 查看import的类是出自哪个jar包的方法

    查看import的类是出自哪个jar包的方法

    下面小编就为大家带来一篇查看import的类是出自哪个jar包的方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-03-03
  • SpringBoot异常处理之异常显示的页面问题

    SpringBoot异常处理之异常显示的页面问题

    这篇文章主要介绍了SpringBoot异常处理异常显示的页面的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-09-09
  • MyBatis-Plus 自定义sql语句的实现

    MyBatis-Plus 自定义sql语句的实现

    这篇文章主要介绍了MyBatis-Plus 自定义sql语句的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • 如何使用JFrame完成动态模拟时钟

    如何使用JFrame完成动态模拟时钟

    本文介绍了如何使用JFrame完成动态模拟时钟,需要的朋友可以参考下
    2015-08-08
  • Java享元设计模式优化对象创建提高性能和效率

    Java享元设计模式优化对象创建提高性能和效率

    Java享元设计模式通过共享可重用的对象,减少了系统中对象的数量,优化了对象的创建和管理,提高了性能和效率。它是一种经典的设计模式,适用于需要处理大量相似对象的应用程序
    2023-04-04
  • Java Bean转Map的那些踩坑实战

    Java Bean转Map的那些踩坑实战

    项目中有时会遇到Map转Bean,Bean转Map的情况,下面这篇文章主要给大家介绍了关于Java Bean转Map那些踩坑的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2022-07-07

最新评论