乐趣区

关于java:深入理解-FilterChainProxy源码篇

昨天有小伙伴加松哥微信,说他把松哥的 Spring Security 系列撸完了。。

but 松哥这个系列还没发完呢,在我的打算中,Spring Security 系列目前应该能更新一半,还剩一半,尽管有的小伙伴可能感觉如同曾经没啥了,其实还有很多货色。。。

松哥最近也是特地忙,Security 更新慢下来了,然而秉持后面说的,要学就成系列的学,要学就学透彻,这个系列我还会持续更上来。

明天咱们就来聊一聊 Spring Security 系列中的 FilterChainProxy。

这是一个十分重要的代理对象。

1. FilterChainProxy

咱们先来回顾一下后面文章讲的:

在一个 Web 我的项目中,申请流程大略如下图所示:

申请从客户端发动(例如浏览器),而后穿过层层 Filter,最终来到 Servlet 上,被 Servlet 所解决。

上图中的 Filter 咱们能够称之为 Web Filter,Spring Security 中的 Filter 咱们能够称之为 Security Filter,它们之间的关系如下图:

能够看到,Spring Security Filter 并不是间接嵌入到 Web Filter 中的,而是通过 FilterChainProxy 来对立治理 Spring Security Filter,FilterChainProxy 自身则通过 Spring 提供的 DelegatingFilterProxy 代理过滤器嵌入到 Web Filter 之中。

DelegatingFilterProxy 很多小伙伴应该比拟相熟,在 Spring 中手工整合 Spring Session、Shiro 等工具时都离不开它,当初用了 Spring Boot,很多事件 Spring Boot 帮咱们做了,所以有时候会感觉 DelegatingFilterProxy 的存在感有所升高,实际上它始终都在。

FilterChainProxy 中能够存在多个过滤器链,如下图:

能够看到,当申请达到 FilterChainProxy 之后,FilterChainProxy 会依据申请的门路,将申请转发到不同的 Spring Security Filters 下面去,不同的 Spring Security Filters 对应了不同的过滤器,也就是不同的申请将通过不同的过滤器。

这是 FilterChainProxy 的一个大抵性能,明天咱们就从源码上了解 FilterChainProxy 中这些性能到底是怎么实现的。

2. 源码剖析

先把 FilterChainProxy 源码亮进去,这个源码比拟上,咱们一部分一部分来,先从它申明的全局属性上开始:

private final static String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");
private List<SecurityFilterChain> filterChains;
private FilterChainValidator filterChainValidator = new NullFilterChainValidator();
private HttpFirewall firewall = new StrictHttpFirewall();
  • FILTER_APPLIED 变量是一个标记,用来标记过滤器是否曾经执行过了。这个标记在 Spring Security 中很常见,松哥这里就不多说了。
  • filterChains 是过滤器链,留神,这个是过滤器链,而不是一个个的过滤器,在【Spring Security 居然能够同时存在多个过滤器链?】一文中,松哥教过大家如何配置多个过滤器链,配置的多个过滤器链就保留在 filterChains 变量中,也就是,如果你有一个过滤器链,这个汇合中就保留一条记录,你有两个过滤器链,这个记录中就保留两条记录,每一条记录又对应了过滤器链中的一个个过滤器。
  • filterChainValidator 是 FilterChainProxy 配置实现后的校验办法,默认应用的 NullFilterChainValidator 实际上对应了一个空办法,也就是不做任何校验。
  • firewall 咱们在后面的文章中也介绍过 (Spring Security 自带防火墙!你都不晓得本人的零碎有多平安!),这里就不再赘述。

接下来咱们来看一个过滤器中最重要的 doFilter 办法:

@Override
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);
            doFilterInternal(request, response, chain);
        }
        finally {SecurityContextHolder.clearContext();
            request.removeAttribute(FILTER_APPLIED);
        }
    }
    else {doFilterInternal(request, response, chain);
    }
}

在 doFilter 办法中,失常来说,clearContext 参数每次都是 true,于是每次都先给 request 标记上 FILTER_APPLIED 属性,而后执行 doFilterInternal 办法去走过滤器,执行结束后,最初在 finally 代码块中革除 SecurityContextHolder 中保留的用户信息,同时移除 request 中的标记。

按着这个程序,咱们来看 doFilterInternal 办法:

private void doFilterInternal(ServletRequest request, ServletResponse response,
        FilterChain chain) throws IOException, ServletException {
    FirewalledRequest fwRequest = firewall
            .getFirewalledRequest((HttpServletRequest) request);
    HttpServletResponse fwResponse = firewall
            .getFirewalledResponse((HttpServletResponse) response);
    List<Filter> filters = getFilters(fwRequest);
    if (filters == null || filters.size() == 0) {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);
        return;
    }
    VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
    vfc.doFilter(fwRequest, fwResponse);
}
private List<Filter> getFilters(HttpServletRequest request) {for (SecurityFilterChain chain : filterChains) {if (chain.matches(request)) {return chain.getFilters();
        }
    }
    return null;
}

doFilterInternal 办法就比拟重要了:

  1. 首先将申请封装为一个 FirewalledRequest 对象,在这个封装的过程中,也会判断申请是否非法。具体参考松哥之前的 Spring Security 自带防火墙!你都不晓得本人的零碎有多平安!一文。
  2. 对响应进行封装。
  3. 调用 getFilters 办法找到过滤器链。该办法就是依据以后的申请,从 filterChains 中找到对应的过滤器链,而后由该过滤器链去解决申请,具体能够参考 Spring Security 居然能够同时存在多个过滤器链?一文。
  4. 如果找进去的 filters 为 null,或者汇合中没有元素,那就是阐明以后申请不须要通过过滤器。间接执行 chain.doFilter,这个就又回到原生过滤器中去了。那么什么时候会产生这种状况呢?那就是针对我的项目中的动态资源,如果咱们配置了资源放行,如 web.ignoring().antMatchers("/hello");,那么当你申请 /hello 接口时就会走到这里来,也就是说这个不通过 Spring Security Filter。这个松哥之前也专门写文章介绍过:Spring Security 两种资源放行策略,千万别用错了!。
  5. 如果查问到的 filters 中是有值的,那么这个 filters 汇合中寄存的就是咱们要通过的过滤器链了。此时它会结构出一个虚构的过滤器链 VirtualFilterChain 进去,并执行其中的 doFilter 办法。

那么接下来咱们就来看看 VirtualFilterChain:

private static 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 {if (currentPosition == size) {if (logger.isDebugEnabled()) {logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
                        + "reached end of additional filter chain; proceeding with original chain");
            }
            // Deactivate path stripping as we exit the security filter chain
            this.firewalledRequest.reset();
            originalChain.doFilter(request, response);
        }
        else {
            currentPosition++;
            Filter nextFilter = additionalFilters.get(currentPosition - 1);
            if (logger.isDebugEnabled()) {logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
                        + "at position" + currentPosition + "of" + size
                        + "in additional filter chain; firing Filter:'"
                        + nextFilter.getClass().getSimpleName() + "'");
            }
            nextFilter.doFilter(request, response, this);
        }
    }
}
  1. VirtualFilterChain 类中首先申明了 5 个全局属性,originalChain 示意原生的过滤器链,也就是 Web Filter;additionalFilters 示意 Spring Security 中的过滤器链;firewalledRequest 示意以后申请;size 示意过滤器链中过滤器的个数;currentPosition 则是过滤器链遍历时候的下标。
  2. doFilter 办法就是 Spring Security 中过滤器挨个执行的过程,如果 currentPosition == size,示意过滤器链曾经执行结束,此时通过调用 originalChain.doFilter 进入到原生过滤链办法中,同时也退出了 Spring Security 过滤器链。否则就从 additionalFilters 取出 Spring Security 过滤器链中的一个个过滤器,挨个调用 doFilter 办法。

最初,FilterChainProxy 中还定义了 FilterChainValidator 接口及其实现:

public interface FilterChainValidator {void validate(FilterChainProxy filterChainProxy);
}
private static class NullFilterChainValidator implements FilterChainValidator {
    @Override
    public void validate(FilterChainProxy filterChainProxy) {}}

实际上这个实现并未做任何事件。

这就是 FilterChainProxy 中的整个逻辑。

3. 小结

其实本文中的很多知识点松哥在之前的文章中都和大家介绍过,例如配置多个过滤器链、StrictHttpFireWall、两种资源放行形式等等,然而之前次要是和大家分享用法,没有去细究他的原理,明天的文章,咱们通过源码把这个问题捋了一遍,置信大家对于这些性能细节也更清晰了。

好啦,感兴趣的小伙伴记得点个在看激励下松哥哦~

退出移动版