过滤器链加载原理

简介: 本文深入解析Spring Security过滤器加载机制,通过源码分析DelegatingFilterProxy、FilterChainProxy与SecurityFilterChain的协作流程,揭示15个安全过滤器如何自动装配并执行,帮助开发者理解框架底层原理,为自定义认证页面奠定基础。

通过前面十五个过滤器功能的介绍,对于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月前
|
安全 Java 数据安全/隐私保护
通用权限管理模型
本文介绍了ACL和RBAC两种常见权限模型。ACL通过直接为用户或角色授权实现控制,简单直观;RBAC则基于角色分配权限,解耦用户与权限关系,更易管理。文中还详解RBAC的四类扩展模型(RBAC0-RBAC3),涵盖角色继承、职责分离等核心设计,帮助构建清晰的权限体系。
通用权限管理模型
|
4月前
|
监控 Java 测试技术
OOM排查之路:一次曲折的线上故障复盘
本文复盘了线上服务因Paimon与RocksDB集成引发的三次OOM故障。从线程激增到堆外内存泄漏,排查过程曲折复杂,最终定位到SDK中RocksDB通过JNI申请内存未释放的根本原因,并通过架构优化解决。分享了MAT、NMT、async-profiler等工具的实战经验,总结了一套系统性的内存问题排查思路,为类似技术栈提供借鉴。
|
4月前
|
存储 缓存 监控
EFC&CTO:缓存引发数据不一致问题排查与深度解析
EFC缓存架构更新后,在CTO测试中出现数据不一致问题。经排查,因分布式缓存版本号回退,导致旧NULL数据被读入pagecache并刷入文件系统,破坏了正常数据。通过维护递增版本号修复,10轮测试验证无误。
 EFC&CTO:缓存引发数据不一致问题排查与深度解析
|
4月前
|
安全 Java 测试技术
从Google线上故障,谈灰度发布的重要性
2025年6月12日,Google Cloud因未灰度发布的配置缺陷导致全球服务中断7小时。本文分析其根因为空指针异常,并详解Nacos等配置中心的灰度发布方案,强调通过IP、标签、流量等多路径实现安全配置变更,提升系统稳定性。
|
4月前
|
消息中间件 监控 Java
RocketMQ:底层Netty频繁OS OOM
本文记录了一例Java应用因Netty多ClassLoader加载导致堆外内存超限引发OS OOM的排查过程。通过NMT、Arthas等工具分析,发现多个中间件独立加载PooledByteBufAllocator,各自绕过MaxDirectMemorySize限制,累计占用远超1G堆外内存。最终定位RocketMQ客户端为主要内存使用者,建议短期调小Java堆以腾出空间,并推动中间件优化。
|
4月前
|
Java 关系型数据库 MySQL
[MES]数据库改造H2到MySQL(☆☆)
本文介绍如何运行并改造一个SpringBoot项目,包括从Gitee克隆代码、环境配置(JDK/Maven)、数据库由H2迁移至MySQL的步骤。强调新人如何高效请教同事、快速适应技术栈,掌握Git、Maven、MyBatis等核心技术,提升实战能力,助力入职后迅速进入角色。
 [MES]数据库改造H2到MySQL(☆☆)
|
4月前
|
自然语言处理 fastjson Java
|
4月前
|
存储 缓存 运维
一场FullGC故障排查
本文记录了一次线上JVM Full GC导致CPU使用率飙升至104%的问题排查与解决过程。通过分析发现,问题根源是将大Excel文件解析为List&lt;Map&gt;结构后长期驻留内存,造成堆内存膨胀,频繁Full GC。结合JProfiler工具定位大对象,最终提出“治本”与“治标”两类优化方案,并总结了JVM性能问题的排查思路与方法。
一场FullGC故障排查

热门文章

最新文章