Spring Security的请求处理流程

Spring Security是基于过滤器链来处理请求的,在Spring Security的启动流程一文中,讲到Spring Security注册到servlet上下文中的过滤器是DelegatingFilterProxy,而该过滤器代理了FilterChainProxy。本文就来分析一下请求是怎么在这些过滤器中流转的。


DelegatingFilterProxy

java
spring-web/src/main/java/org/springframework/web/filter/DelegatingFilterProxy.java
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {

    // Lazily initialize the delegate if necessary.
    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.
    // 调用目标过滤器
    invokeDelegate(delegateToUse, request, response, filterChain);
}
protected void invokeDelegate(
        Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {

    delegate.doFilter(request, response, filterChain);
}

这里首先是获取代理目标,一般来说在初始化阶段就会获取了,这里运行时判断是否是null,是的话会再次获取目标过滤器。最后调用目标过滤器的doFilter方法。

FilterChainProxy

java
web/src/main/java/org/springframework/security/web/FilterChainProxy.java
/*
 * 该类型的实例在WebSecurity的performBuild()方法中被创建,
 * 被WebSecurityConfiguration中的springSecurityFilterChain()方法注入到容器中(该filter bean的名称为springSecurityFilterChain),
 * 该方法在DelegatingFilterProxy中的invokeDelegate()方法中被调用
 */
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
    if (!clearContext) { // 已经设置过了FILTER_APPLIED属性,为什么已经设置过了该属性的的请求还会被该过滤器处理?
        // 执行过滤器
        doFilterInternal(request, response, chain);
        return;
    }
    try {
        request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
        // 执行过滤器
        doFilterInternal(request, response, chain);
    }
    catch (Exception ex) {
        Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
        Throwable requestRejectedException = this.throwableAnalyzer
            .getFirstThrowableOfType(RequestRejectedException.class, causeChain);
        if (!(requestRejectedException instanceof RequestRejectedException)) {
            throw ex;
        }
        // 拒绝处理
        this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response,
                (RequestRejectedException) requestRejectedException);
    }
    finally {
        this.securityContextHolderStrategy.clearContext();
        request.removeAttribute(FILTER_APPLIED);
    }
}

该方法只是对异常进行了处理,具体的实现在doFilterInternal方法中。

java
web/src/main/java/org/springframework/security/web/FilterChainProxy.java
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) {
        if (logger.isTraceEnabled()) {
            logger.trace(LogMessage.of(() -> "No security for " + requestLine(firewallRequest)));
        }
        firewallRequest.reset();
        this.filterChainDecorator.decorate(chain).doFilter(firewallRequest, firewallResponse);
        return;
    }
    if (logger.isDebugEnabled()) {
        logger.debug(LogMessage.of(() -> "Securing " + requestLine(firewallRequest)));
    }
    // 封装了后续其他的servlet过滤器
    FilterChain reset = (req, res) -> {
        if (logger.isDebugEnabled()) {
            logger.debug(LogMessage.of(() -> "Secured " + requestLine(firewallRequest)));
        }
        // Deactivate path stripping as we exit the security filter chain
        firewallRequest.reset();
        // 继续调用后续的servlet filter
        chain.doFilter(req, res);
    };
    // filterChainDecorator是VirtualFilterChain类型的
    this.filterChainDecorator.decorate(reset, filters).doFilter(firewallRequest, firewallResponse);
}

该方法中先是根据请求获取目标过滤器列表,然后是利用过滤器装饰器对过滤器列表进行装饰,形成一条过滤器链,最后调用这条过滤器链。

获取目标过滤器列表

java
web/src/main/java/org/springframework/security/web/FilterChainProxy.java
private List<Filter> getFilters(HttpServletRequest request) {
    int count = 0;
    // 遍历过滤器链
    for (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;
}

这里遍历了过滤器链列表,然后调用它的matches方法来筛选出能够处理该请求的过滤器。这里的过滤器链是在WebSecurityperformBuild方法中创建时,通过构造方法参数传入的。一般这里就只有一条过滤器链,就是我们自定义并注册到bean工厂中的那个bean对象。我们通过HttpSecurity来构建的过滤器链是DefaultSecurityFilterChain类型的。

java
config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java
@SuppressWarnings("unchecked")
@Override
protected DefaultSecurityFilterChain performBuild() {
    // URL表达式授权配置器
    ExpressionUrlAuthorizationConfigurer<?> expressionConfigurer = getConfigurer(
            ExpressionUrlAuthorizationConfigurer.class);
    // http授权配置器
    AuthorizeHttpRequestsConfigurer<?> httpConfigurer = getConfigurer(AuthorizeHttpRequestsConfigurer.class);
    // 这里使用了异或运算符(^)
    boolean oneConfigurerPresent = expressionConfigurer == null ^ httpConfigurer == null;
    // 如果都为null或都不为null,则断言失败,请选一个使用
    Assert.state((expressionConfigurer == null && httpConfigurer == null) || oneConfigurerPresent,
            "authorizeHttpRequests cannot be used in conjunction with authorizeRequests. Please select just one.");

    // 对过滤器进行排序
    this.filters.sort(OrderComparator.INSTANCE);
    List<Filter> sortedFilters = new ArrayList<>(this.filters.size());
    for (Filter filter : this.filters) {
        // 添加到列表中
        sortedFilters.add(((OrderedFilter) filter).filter);
    }
    // 创建过滤器链
    return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);
}

调用的getFilter方法返回的过滤器列表就是上面方法中的变量sortedFilters,这里通过对实例属性filters来封装的。我们可以直接调用HttpSecurity添加过滤器器的方法来添加过滤器,也可以通过不同的配置器(configurer)对象来配置逻辑,在这些配置器初始化的时候也会创建相应的过滤器。

进行装饰

FilterChainProxy中创建的装饰器对象是VirtualFilterChainDecorator类型的。

java
web/src/main/java/org/springframework/security/web/FilterChainProxy.java
public static final class VirtualFilterChainDecorator implements FilterChainDecorator {

    /**
     * {@inheritDoc}
     */
    @Override
    public FilterChain decorate(FilterChain original) {
        return original;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public FilterChain decorate(FilterChain original, List<Filter> filters) {
        return new VirtualFilterChain(original, filters);
    }

}

这里将多个过滤器封装为VirtualFilterChain

执行过滤器链

java
web/src/main/java/org/springframework/security/web/FilterChainProxy.java
private static final class VirtualFilterChain implements FilterChain {

    // 其他的servlet上下文中的过滤器
    private final FilterChain originalChain;

    // Spring Security中的过滤器列表
    private final List<Filter> additionalFilters;

    private final int size;

    private int currentPosition = 0;

    private VirtualFilterChain(FilterChain chain, List<Filter> additionalFilters) {
        this.originalChain = chain;
        this.additionalFilters = additionalFilters;
        this.size = additionalFilters.size();
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        // 如果已经执行完security相关的过滤器
        if (this.currentPosition == this.size) {
            // 则让其他servlet的filter被执行
            this.originalChain.doFilter(request, response);
            return;
        }
        this.currentPosition++;
        // 获取下一个待执行的过滤器
        Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
        if (logger.isTraceEnabled()) {
            String name = nextFilter.getClass().getSimpleName();
            logger.trace(LogMessage.format("Invoking %s (%d/%d)", name, this.currentPosition, this.size));
        }
        // 调用过滤器,并传入this作为过滤器链,过滤器内部可以决定要不要执行后面的其他过滤器。
        nextFilter.doFilter(request, response, this);
    }

}

doFilter方法中,依次从列表中获取过滤器,并调用其doFilter方法,并传入this作为过滤器链。在执行完了Spring Security中的过滤器后,会执行其他servlet中的过滤器。上面方法中的nextFilter变量就是我们在业务中自定义的过滤器。