2.过滤器链加载原理

简介: 本文深入解析Spring Security过滤器加载机制,通过分析DelegatingFilterProxy、FilterChainProxy与SecurityFilterChain源码,揭示十五个安全过滤器如何自动装配并形成过滤链,帮助理解框架底层工作原理,为自定义认证页面奠定基础。

通过前面十五个过滤器功能的介绍,对于SpringSecurity简单入门中的疑惑是不是在心中已经有了答案了呀? 但新的问题来了!我们并没有配置这些过滤器啊?它们都是怎么被加载出来的?

1-DelegatingFilterProxy

我们在web.xml中配置了一个名称为springSecurityFilterChain的过滤器DelegatingFilterProxy,接下我直接对 DelegatingFilterProxy源码里重要代码进行说明,其中删减掉了一些不重要的代码,大家注意我写的注释就行了!

public class DelegatingFilterProxy extends GenericFilterBean {
    @Nullable 
    private String contextAttribute; 
    @Nullable 
    private WebApplicationContext webApplicationContext; 
    @Nullable 
    private String targetBeanName; 
    private boolean targetFilterLifecycle; 
    @Nullable 
    private volatile Filter delegate;//注:这个过滤器才是真正加载的过滤器 
    private final Object delegateMonitor; 
    //注:doFilter才是过滤器的入口,直接从这看! 
    public void doFilter(ServletRequest request, 
                         ServletResponse response, 
                         FilterChain 
                         filterChain) throws ServletException, IOException { 
        Filter delegateToUse = this.delegate; 
        if (delegateToUse == null) { 
            synchronized(this.delegateMonitor) { 
                delegateToUse = this.delegate; 
                if (delegateToUse == null) { 
                    WebApplicationContext wac = this.findWebApplicationContext(); 
                    if (wac == null) { 
                        throw new IllegalStateException("No WebApplicationContext found: no 
                        ContextLoaderListener or DispatcherServlet registered?"); 
                    } 
                    //第一步:doFilter中最重要的一步,初始化上面私有过滤器属性delegate 
                    delegateToUse = this.initDelegate(wac); 
                } 
                this.delegate = delegateToUse; 
            } 
        } 
        //第三步:执行FilterChainProxy过滤器 
        this.invokeDelegate(delegateToUse, request, response, filterChain); 
    } 
    //第二步:直接看最终加载的过滤器到底是谁 
    protected Filter initDelegate(WebApplicationContext wac) throws ServletException { 
        //debug得知targetBeanName为:springSecurityFilterChain 
        String targetBeanName = this.getTargetBeanName(); 
        Assert.state(targetBeanName != null, "No target bean name set"); 
        //debug得知delegate对象为:FilterChainProxy 
        Filter delegate = (Filter)wac.getBean(targetBeanName, Filter.class); 
        if (this.isTargetFilterLifecycle()) { 
            delegate.init(this.getFilterConfig()); 
      } 
    return delegate; 
    }
}

第二步debug结果如下:

由此可知,DelegatingFilterProxy通过springSecurityFilterChain这个名称,得到了一个FilterChainProxy过滤器, 最终在第三步执行了这个过滤器。

2-FilterChainProxy

注意代码注释!

public class FilterChainProxy extends GenericFilterBean {
    private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
    private static final String FILTER_APPLIED =
    FilterChainProxy.class.getName().concat(".APPLIED");
    private List<SecurityFilterChain> filterChains;
    private FilterChainProxy.FilterChainValidator filterChainValidator;
    private HttpFirewall firewall;
    //咿!?可以通过一个叫SecurityFilterChain的对象实例化出一个FilterChainProxy对象
    //这FilterChainProxy又是何方神圣?会不会是真正的过滤器链对象呢?先留着这个疑问!
    public FilterChainProxy(SecurityFilterChain chain) {
        this(Arrays.asList(chain));
    }
    //又是SecurityFilterChain这家伙!嫌疑更大了!
    public FilterChainProxy(List<SecurityFilterChain> filterChains) {
        this.filterChainValidator = new FilterChainProxy.NullFilterChainValidator();
        this.firewall = new StrictHttpFirewall();
        this.filterChains = filterChains;
    }
    //注:直接从doFilter看
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException {
        boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
        if (clearContext) {
            try {
                request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
                this.doFilterInternal(request, response, chain);
            } finally {
                SecurityContextHolder.clearContext();
                request.removeAttribute(FILTER_APPLIED);
            }
        } else {
            //第一步:具体操作调用下面的doFilterInternal方法了
            this.doFilterInternal(request, response, chain);
        }
    }
    private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain
                                  chain) throws IOException, ServletException {
        FirewalledRequest fwRequest =
        this.firewall.getFirewalledRequest((HttpServletRequest)request);
        HttpServletResponse fwResponse =
        this.firewall.getFirewalledResponse((HttpServletResponse)response);
        //第二步:封装要执行的过滤器链,那么多过滤器就在这里被封装进去了!
        List<Filter> filters = this.getFilters((HttpServletRequest)fwRequest);
        if (filters != null && filters.size() != 0) {
            FilterChainProxy.VirtualFilterChain vfc = new
            FilterChainProxy.VirtualFilterChain(fwRequest, chain, filters);
            //第四步:加载过滤器链
            vfc.doFilter(fwRequest, fwResponse);
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug(UrlUtils.buildRequestUrl(fwRequest) 
                             + (filters == null ? " has no matching filters" : " has an empty filter list"));
            }
            fwRequest.reset();
            chain.doFilter(fwRequest, fwResponse);
        }
    }
    private List<Filter> getFilters(HttpServletRequest request) {
        Iterator var2 = this.filterChains.iterator();
        //第三步:封装过滤器链到SecurityFilterChain中!
        SecurityFilterChain chain;
        do {
            if (!var2.hasNext()) {
                return null;
            }
                chain = (SecurityFilterChain)var2.next();
    } while(!chain.matches(request));
    return chain.getFilters();
  }
}

第二步debug结果如下图所示,惊不惊喜?十五个过滤器都在这里了!

再看第三步,怀疑这么久!原来这些过滤器还真是都被封装进SecurityFilterChain中了。

3-SecurityFilterChain

最后看SecurityFilterChain,这是个接口,实现类也只有一个,这才是web.xml中配置的过滤器链对象!

//接口
public interface SecurityFilterChain {
    boolean matches(HttpServletRequest var1);
    List<Filter> getFilters();
}
//实现类
public final class DefaultSecurityFilterChain implements SecurityFilterChain {
    
    private static final Log logger = LogFactory.getLog(DefaultSecurityFilterChain.class);
    private final RequestMatcher requestMatcher;
    private final List<Filter> filters;
    
    public DefaultSecurityFilterChain(RequestMatcher requestMatcher,
                                      Filter... filters) {
        this(requestMatcher, Arrays.asList(filters));
    }
    
    public DefaultSecurityFilterChain(RequestMatcher requestMatcher, 
                                      List<Filter> filters) {
        logger.info("Creating filter chain: " + requestMatcher + ", " + filters);
        this.requestMatcher = requestMatcher;
        this.filters = new ArrayList(filters);
    }
    
    public RequestMatcher getRequestMatcher() {
        return this.requestMatcher;
    }
    
    public List<Filter> getFilters() {
        return this.filters;
    }
    
    public boolean matches(HttpServletRequest request) {
        return this.requestMatcher.matches(request);
    }
    
    public String toString() {
        return "[ " + this.requestMatcher + ", " + this.filters + "]";
    }
}

总结:通过此章节,我们对SpringSecurity工作原理有了一定的认识。但理论千万条,功能第一条,探寻底层,是为了更好的使用框架。 那么,言归正传!到底如何使用自己的页面来实现SpringSecurity的认证操作呢?要完成此功能,首先要有一套自己的页面!

相关文章
|
4月前
|
负载均衡 应用服务中间件 Nacos
Nacos配置中心
本文详细讲解了Nacos作为配置中心的核心功能与实践应用,涵盖配置管理、热更新、共享配置及优先级规则,并通过搭建Nacos集群实现高可用部署,帮助开发者掌握微服务环境下配置的集中化管理方案。
 Nacos配置中心
|
4月前
|
NoSQL Linux 网络安全
Redis集群部署指南
本章基于CentOS7搭建Redis集群,涵盖单机安装、主从复制、哨兵高可用及分片集群配置,详细演示Redis集群部署与管理全过程。
Redis集群部署指南
|
4月前
|
SpringCloudAlibaba Java Nacos
SpringCloud概述
Spring Cloud是微服务架构的综合解决方案,由Spring团队推出,具备约定大于配置、组件丰富、开箱即用等特点。为解决Netflix组件停更问题,阿里推出Spring Cloud Alibaba,集成Nacos、Sentinel、Seata等高性能中间件,成为主流技术栈选择。
|
4月前
|
负载均衡 Java 数据安全/隐私保护
Gateway服务网关
本文介绍了微服务架构中网关的核心作用,包括请求路由、权限控制和限流等功能,重点讲解了Spring Cloud Gateway的使用方法。内容涵盖网关的快速搭建、路由配置、断言与过滤器工厂的应用,并通过实际案例演示了如何实现全局过滤器与跨域问题的解决方案,帮助开发者掌握微服务统一入口的关键技术。
 Gateway服务网关
|
4月前
|
存储 缓存 算法
零拷贝
实现文件传输时,传统方式因频繁系统调用导致大量上下文切换与内存拷贝,性能低下。零拷贝技术通过合并系统调用、减少用户态与内核态切换,并利用PageCache和SG-DMA,显著降低开销。大文件场景下可结合异步IO与直接IO,避免缓存污染,提升并发性能。
|
4月前
|
编解码 数据安全/隐私保护
性能优化专题
本文包含两幅带水印的图片,内容无法直接查看,可能为文档或设计稿的插图。图片分辨率分别为1080×756和1080×1697,均居左显示,用于辅助说明上下文信息。
 性能优化专题
|
4月前
|
自然语言处理 fastjson Java
FastJson:大面积故障规避案例
本文记录了一次由Kotlin语法混淆引发的FastJson反序列化重大故障排查过程。因误将`{}`赋值给Java对象字段,导致FastJson解析时触发Kotlin静态错误标记,进而引发全局反序列化失败。问题根源隐蔽,影响巨大,最终通过深入源码定位解决。揭示了多语言混编下语法差异的风险,以及对第三方框架需保持警惕。
 FastJson:大面积故障规避案例
|
4月前
|
Java 测试技术 Linux
幂等方案专题
本文介绍大型团队如何通过自动化部署平台实现多环境(dev/test/pre/prod)高效发布,涵盖各环境职责、基于Jenkins+K8S的CI/CD流程、分支管理与日志排查方案,并对比不同部署模式下的问题应对策略。
|
4月前
|
Dubbo Java 应用服务中间件
1.入门运行Soul
Soul 是基于 WebFlux 的高性能响应式 API 网关,支持 Dubbo、Spring Cloud、HTTP 等多协议,具备插件化、热插拔、动态流量控制、A/B 测试等特性,支持集群部署与多种数据同步方式,适用于高并发微服务架构。
|
4月前
|
存储 安全 数据库
1.RememberMe简介及用法
RememberMe功能并非简单保存用户名密码,而是通过服务端生成令牌(Token)实现持久登录。用户勾选后,系统在Cookie中存储令牌,关闭浏览器后仍可免密登录。但令牌泄露存在安全风险,可通过将Token持久化到数据库并增加二次校验机制提升安全性。