spring boot web拦截器、过滤器与 Spring Security 协作机制

4 阅读53分钟

概述

前文已经深入剖析了 Spring MVC 的启动与请求处理链路。在 DispatcherServlet.doDispatch 的入口处,我们已经看到了拦截器链的执行。但实际项目中,请求在抵达 DispatcherServlet 之前,还要经过一道道 Servlet Filter 以及 Spring Security 的安全过滤器。本文将视野拓展到整个 Web 容器层,详细拆解这三层拦截机制是如何协同工作的,以及 Spring Security 如何借助 Spring 的 IoC 容器和扩展点,成为最强大的安全守护者。

在 Spring Web 应用中,一个 HTTP 请求从进入 Servlet 容器到最终被 Controller 方法处理,至少要穿过三层拦截网:Servlet Filter、Spring Security Filter Chain、HandlerInterceptor。这三者职责各有侧重:Filter 擅长处理通用的、粗粒度的容器级任务;Spring Security 过滤器专门负责认证、授权与安全上下文管理;HandlerInterceptor 则聚焦于 Spring MVC 框架层的预处理和后处理。Spring 通过 DelegatingFilterProxy 这一精巧的设计,将 IoC 容器中的安全过滤器无缝接入到 Servlet 容器中,让原本与 Spring 无关的 Filter 也享受了依赖注入和生命周期管理的好处。本文将沿着请求的流经轨迹,逐一拆解这三层拦截的注册、执行顺序与协作细节,彻底梳理 Spring 安全防护的底层逻辑。

核心要点:

  • 三层拦截架构:Servlet Filter → Spring Security Filter Chain → HandlerInterceptor。
  • DelegatingFilterProxy 的桥梁作用:将 IoC 容器中的 Spring Bean 暴露为标准 Servlet Filter。
  • FilterChainProxy 的智能匹配:持有多套 SecurityFilterChain,根据请求路径选择不同的安全策略。
  • 执行顺序与优先级:从 FilterOrderRegistrationInterceptorRegistry 的顺序控制。
  • 扩展点与设计模式:责任链模式在三层拦截中的统一体现,以及 Spring 如何通过 FilterRegistrationBeanWebMvcConfigurer 提供扩展。
flowchart TD
    subgraph S1 ["1. 三层拦截体系总览"]
        direction TB
        A["HTTP请求"] --> B["Servlet Filter"]
        B --> C["Spring Security Filter Chain"]
        C --> D["HandlerInterceptor"]
        D --> E["Controller"]
    end

    subgraph S2 ["2. Servlet Filter深度剖析"]
        direction TB
        B --> F["Filter接口"]
        F --> G["FilterChain链式调用"]
        G --> H["FilterRegistrationBean注册"]
    end

    subgraph S3 ["3. HandlerInterceptor机制"]
        direction TB
        D --> I["preHandle/postHandle/afterCompletion"]
        I --> J["HandlerExecutionChain"]
    end

    subgraph S4 ["4. Spring Security过滤器链核心"]
        direction TB
        C --> K["DelegatingFilterProxy"]
        K --> L["FilterChainProxy"]
        L --> M["SecurityFilterChain"]
    end

    subgraph S5 ["5. 安全过滤器序列"]
        direction TB
        N["SecurityContextPersistenceFilter"]
        O["UsernamePasswordAuthenticationFilter"]
        P["ExceptionTranslationFilter"]
        Q["FilterSecurityInterceptor"]
    end

    subgraph S6 ["6. 执行顺序与协作全景"]
        X6["(整合各层协作要点)"]
    end

    subgraph S7 ["7. 生产事故排查专题"]
        X7["(排查案例与工具)"]
    end

    subgraph S8 ["8. 面试高频专题"]
        X8["(面试题与系统设计)"]
    end

    S1 --> S2
    S1 --> S3
    S1 --> S4
    S4 --> S5
    S2 & S3 & S5 --> S6
    S6 --> S7
    S6 --> S8

    M --> N
    M --> O
    M --> P
    M --> Q

    classDef topic fill:#f8f9fa,stroke:#333,stroke-width:2px,rx:5,color:#333;
    classDef placeholder fill:#e9ecef,stroke:#adb5bd,stroke-dasharray: 5 5,color:#555;
    class S1,S2,S3,S4,S5,S6,S7,S8 topic;
    class X6,X7,X8 placeholder;

架构图说明

  • 总览说明:本文遵循从宏观到微观再到实践的思路。模块 1 建立全局视野,模块 2、3、4、5 深入各层核心组件,模块 6 将这些分散的点连接成一条完整的请求处理线,模块 7 和 8 则将理论应用于实践,解决生产问题和面试挑战。
  • 逐模块说明
    • 模块 1:建立一个 HTTP 请求经过的完整路径,从 Servlet 容器到 DispatcherServlet,再到具体的 Controller。
    • 模块 2:聚焦 Servlet 规范定义的 Filter 组件,剖析其接口、链式调用模型和在 Spring Boot 中的注册机制。
    • 模块 3:深入 Spring MVC 特有的 HandlerInterceptor 组件,分析其三个阶段回调与 DispatcherServlet 的集成点。
    • 模块 4:揭示 Spring Security 与 Web 容器整合的秘密,重点分析 DelegatingFilterProxyFilterChainProxy 这两个核心桥梁类。
    • 模块 5:拆解 Spring Security 默认的过滤器链,逐一说明每个核心过滤器的职责和它们如何协同构建安全上下文。
    • 模块 6:将三层拦截机制放入一条时间线进行对比,清晰展示每一步的执行顺序和异常传播路径。
    • 模块 7:通过真实的生产事故案例,反向印证和巩固前三层的协作原理,培养排查问题的能力。
    • 模块 8:系统梳理相关面试考点,提供深度回答和多角度追问,助力技术面试。
  • 关键结论掌握 Filter、FilterChainProxy、Interceptor 的执行顺序与生命周期差异,是排查 Spring Web 安全与请求异常的基本功。

1. 三层拦截体系总览:Filter → SecurityFilterChain → Interceptor

一个 HTTP 请求在抵达业务 Controller 之前,会在 Web 容器和 Spring 框架中经历一个精心设计的拦截体系。这个体系可以被清晰地分为三个层次,每一层都有其独特的职责、生命周期和设计哲学。

  1. Servlet Filter 层:这是最外层,由 Servlet 容器(如 Tomcat、Jetty)直接管理。它是 Servlet 规范的一部分,与 Spring 框架无关。Filter 的主要任务是处理通用的、粗粒度的切面需求,例如:

    • 日志记录:记录所有进入应用的原始请求和原始响应。
    • 字符编码:统一设置请求和响应的字符集(如 CharacterEncodingFilter)。
    • 请求包装:对请求进行二次封装,例如实现 body 的多次读取。
    • 跨域处理:在响应中添加 CORS 头。 它的作用范围是所有匹配到的 URL,功能强大但相对底层,无法直接访问 Spring MVC 的 Handler(Controller 方法)或模型视图对象。
  2. Spring Security Filter Chain 层:这本质上也是一个或多个 Servlet Filter,但它是一个设计精巧的、由 Spring IoC 容器管理的过滤器链。通过 DelegatingFilterProxy 这个标准 Filter 作为代理,它将请求从 Servlet 容器“桥接”到 Spring 的世界。这一层专职于安全,其内部的过滤器链负责:

    • 认证:验证用户身份(你是谁)。
    • 授权:判断用户是否有权访问资源(你能干什么)。
    • 会话管理:管理 SecurityContext
    • 攻击防护:如 CSRF、Session Fixation 等。 它的位置通常在自定义 Servlet Filter 和应用自身的业务 Filter 之间,是安全领域的事实标准。
  3. HandlerInterceptor 层:这是 Spring MVC 框架层的一部分,由 DispatcherServlet 调度执行。它的拦截粒度更细,可以深度参与到 Handler 的执行生命周期中。其主要职责包括:

    • 预处理:在 Handler 执行前进行权限检查、记录调用开始时间。
    • 后处理:在 Handler 执行后但视图渲染前修改 ModelAndView 或进行其他处理。
    • 完成处理:在视图渲染完成后进行资源清理(无论是否发生异常)。 与 Filter 最大的不同是,Interceptor 可以确切知道当前请求将要执行或已经执行的是哪个 Handler,并能访问 ModelAndView

这三层构成了一个立体、稳固的请求拦截体系。外层的 Filter 筑起通用和高性能防护的第一道防线;中间的 Spring Security Filter 接管所有安全相关的复杂逻辑;内层的 Interceptor 则进行与业务逻辑紧密相关的框架级处理。Spring Security 通过巧妙的代理设计,完美地嵌入了 Servlet 标准体系和 Spring IoC 容器之间,成为了两者之间的桥梁和黏合剂。

2. Servlet Filter 的注册、生命周期与链式调用

Servlet Filter 是整个拦截体系的基石。理解其接口和生命周期是理解后续所有内容的前提。

2.1 Filter 接口与生命周期

在 Servlet 3.1 规范中,javax.servlet.Filter 接口定义了三个核心方法:

package javax.servlet;

import java.io.IOException;

public interface Filter {
    // 初始化方法,在Filter被容器实例化后调用,只调用一次。
    // filterConfig 可用于获取web.xml或注解中配置的初始化参数。
    public default void init(FilterConfig filterConfig) throws ServletException {}

    // 核心方法,每次请求/响应通过过滤器链时调用。
    // request/response 是Servlet容器传递的原始请求和响应对象。
    // chain 用于调用过滤器链中的下一个Filter或目标资源。
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException;

    // 销毁方法,当Filter被容器移除或应用关闭时调用,只调用一次。
    public default void destroy() {}
}

一个 Filter 的生命周期由 Servlet 容器全权管理:

  1. 实例化与初始化:Web 容器启动时,根据配置创建 Filter 实例,并调用其 init(FilterConfig) 方法。这是一个完美的时机来加载配置,或建立资源连接。
  2. 请求拦截:当一个请求到来,且其 URL 匹配该 Filter 的映射时,容器会调用其 doFilter(ServletRequest, ServletResponse, FilterChain) 方法。Filter 通过调用 chain.doFilter(request, response) 将控制权传递给链条中的下一个组件。如果不调用此方法,请求将被拦截,不再继续传递,这就是典型的权限拦截或请求短路。
  3. 销毁:当应用关闭或 Filter 被显式移除时,容器调用其 destroy() 方法,用于进行资源回收。

2.2 FilterChain 的责任链模型

javax.servlet.FilterChain 接口极其简单,只有一个方法,但蕴含了责任链模式的精髓。

package javax.servlet;

import java.io.IOException;
import javax.servlet.ServletException;

public interface FilterChain {
    // 调用链中的下一个Filter,如果当前是最后一个Filter,则调用目标资源(如Servlet)。
    public void doFilter(ServletRequest request, ServletResponse response) 
                         throws IOException, ServletException;
}

在 Tomcat 等容器内部,FilterChain 的实现(如 ApplicationFilterChain)持有一个 Filter 数组和一个当前位置索引 pos。每次调用 doFilterpos 递增并调用下一个 Filter 的 doFilter 方法。当 pos 等于 Filter 总数时,请求最终被分发给目标 Servlet。这是一个典型的、基于数组迭代实现的责任链模式。

2.3 Filter 注册方式的演进

  • 传统方式:web.xml

    <filter>
        <filter-name>loggingFilter</filter-name>
        <filter-class>com.example.LoggingFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>loggingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    这是最早期也最直观的配置方式,Filter 的顺序由其在 web.xml 中的声明顺序决定。这种方式完全与代码分离,但不利于编程式控制。

  • Servlet 3.0+ 注解驱动:@WebFilter

    @WebFilter(urlPatterns = "/*", filterName = "loggingFilter")
    public class LoggingFilter implements Filter {
        // ...实现细节
    }
    

    注解消除了 web.xml 配置,使开发更便捷。但 @WebFilter 的致命缺陷是无法精确控制 Filter 的执行顺序。其顺序由 Servlet 容器的实现决定,通常基于类的全限定名,这在需要保证严格顺序的安全场景中是不可接受的。

  • Spring Boot 编程式注册:FilterRegistrationBean Spring Boot 提供了 FilterRegistrationBean 作为 @WebFilter 的替代方案,将其纳入 IoC 容器的管理,并解决了顺序控制问题。

    @Configuration
    public class WebConfig {
        @Bean
        public FilterRegistrationBean<LoggingFilter> loggingFilterRegistration() {
            FilterRegistrationBean<LoggingFilter> registration = new FilterRegistrationBean<>();
            registration.setFilter(new LoggingFilter()); // 注册自定义Filter
            registration.addUrlPatterns("/*");           // 设置匹配路径
            registration.setOrder(1);                    // 精确控制顺序,数字越小优先级越高
            return registration;
        }
    }
    

    FilterRegistrationBean 通过 setOrder(int) 方法,允许开发者以数值形式精确控制 Filter 在 Filter Chain 中的位置。其底层原理是 Spring Boot 将 FilterRegistrationBean 管理的 Filter 注册到 Servlet 容器时,会通过 FilterRegistrationBeangetOrder() 返回值来设置优先级。它实现了 OrderedPriorityOrdered 接口,与 Spring 的通用顺序管理机制完美融合。

2.4 内联示例:观察 Filter 执行顺序

以下是一个简单的可运行示例,用于观察两个自定义 Filter 的执行顺序。

// 1. 第一个Filter: FirstFilter.java
public class FirstFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("FirstFilter: Before chain.doFilter");
        chain.doFilter(request, response); // 传递请求
        System.out.println("FirstFilter: After chain.doFilter");
    }
}

// 2. 第二个Filter: SecondFilter.java
public class SecondFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("SecondFilter: Before chain.doFilter");
        chain.doFilter(request, response);
        System.out.println("SecondFilter: After chain.doFilter");
    }
}

// 3. 注册配置: FilterConfig.java
@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean<FirstFilter> firstFilterRegistration() {
        FilterRegistrationBean<FirstFilter> bean = new FilterRegistrationBean<>(new FirstFilter());
        bean.addUrlPatterns("/*");
        bean.setOrder(1); // 设置为较高优先级
        return bean;
    }

    @Bean
    public FilterRegistrationBean<SecondFilter> secondFilterRegistration() {
        FilterRegistrationBean<SecondFilter> bean = new FilterRegistrationBean<>(new SecondFilter());
        bean.addUrlPatterns("/*");
        bean.setOrder(2); // 设置为较低优先级
        return bean;
    }
}

预期输出

FirstFilter: Before chain.doFilter
SecondFilter: Before chain.doFilter
... (DispatcherServlet、Interceptor、Controller等) ...
SecondFilter: After chain.doFilter
FirstFilter: After chain.doFilter

这个“洋葱模型”般的输出完美展示了 Filter 栈式执行的顺序:请求先进入优先级最高的 Filter(Order=1),后退出;后进入优先级最低的 Filter,先退出。理解这个栈式执行顺序对于排查 Filter 相关问题至关重要。

3. HandlerInterceptor:MVC 框架层的拦截利器

当请求穿过所有 Filter,抵达 DispatcherServlet 后,另一套更精细的拦截机制登场——HandlerInterceptor。它不再是 Servlet 规范,而是 Spring MVC 框架提供的扩展点。

3.1 三阶段拦截

org.springframework.web.servlet.HandlerInterceptor 接口定义了三个回调点,对应于请求在 DispatcherServlet 中处理的不同阶段:

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;

public interface HandlerInterceptor {
    // 1. 前置处理:在Handler执行前调用。
    // 返回true: 继续执行拦截器链和Handler。
    // 返回false: 中断执行,后续拦截器和Handler不再执行。
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
            throws Exception {
        return true;
    }

    // 2. 后置处理:在Handler成功执行后,视图渲染前调用。
    // 可以通过ModelAndView对视图对象进行修改。
    // 如果preHandle返回false,此方法不会被执行。
    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                            @Nullable ModelAndView modelAndView) throws Exception {
    }

    // 3. 完成处理:在整个请求完成后(视图渲染完毕),且无论如何都会执行(preHandle返回true时)。
    // 适合进行资源清理等操作。可以感知到Handler抛出的异常。
    // 注意:preHandle返回false后,此方法不会被执行。
    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                                 @Nullable Exception ex) throws Exception {
    }
}

3.2 源码分析:HandlerExecutionChain 的驱动

HandlerInterceptor 并非独立工作,它由 HandlerExecutionChain 进行管理和驱动。回顾前文,在 DispatcherServlet.doDispatch 方法中,会调用 getHandler 返回一个 HandlerExecutionChain 对象,它封装了目标 Handler 和一系列的 HandlerInterceptor。

源码位置org.springframework.web.servlet.DispatcherServlet.doDispatch

// 在DispatcherServlet.doDispatch方法内部简化逻辑
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    // ...
    try {
        // ...
        // 1. 通过HandlerMapping获取HandlerExecutionChain(内含Handler和所有匹配的Interceptor)
        mappedHandler = getHandler(processedRequest);
        
        // ...
        // 2. 执行所有拦截器的preHandle方法
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            // 如果任一拦截器返回false,请求在此终止
            return;
        }

        // 3. 实际调用Handler执行业务逻辑
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

        // ...
        // 4. 执行所有拦截器的postHandle方法(反向执行)
        mappedHandler.applyPostHandle(processedRequest, response, mv);
    }
    catch (Exception ex) {
        dispatchException = ex;
    }
    // ...
    finally {
        // 5. 无论如何,在最后都触发所有拦截器的afterCompletion方法(反向执行)
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

接着看 HandlerExecutionChain 的执行细节:

源码位置org.springframework.web.servlet.HandlerExecutionChain.applyPreHandle

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 获取所有注册的拦截器
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        // 正向遍历所有拦截器
        for (int i = 0; i < interceptors.length; i++) {
            HandlerInterceptor interceptor = interceptors[i];
            // 调用每个拦截器的preHandle
            if (!interceptor.preHandle(request, response, this.handler)) {
                // 如果返回false,立即反向触发已执行拦截器的afterCompletion
                triggerAfterCompletion(request, response, null);
                return false;
            }
            // 记录当前已成功执行preHandle的拦截器索引,用于反向触发afterCompletion
            this.interceptorIndex = i;
        }
    }
    return true;
}

// applyPostHandle和triggerAfterCompletion均以反向方式遍历拦截器。
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        // 从interceptorIndex开始反向遍历,确保只清理已执行preHandle的拦截器
        for (int i = this.interceptorIndex; i >= 0; i--) {
            HandlerInterceptor interceptor = interceptors[i];
            try {
                interceptor.afterCompletion(request, response, this.handler, ex);
            }
            catch (Throwable ex2) {
                logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
            }
        }
    }
}

设计意图解读

  • 责任链模式:与 Filter 的 FilterChain 类似,HandlerExecutionChain 也是对责任链模式的实现。它遍历拦截器列表,并决定了何时继续、何时中断。
  • 双向遍历:正向遍历执行 preHandle,反向遍历执行 postHandleafterCompletion。这种设计模仿了 Filter 的“栈”式调用结构,保证了处理和清理逻辑的对称性。
  • 安全中断:当某个 preHandle 返回 false 时,applyPreHandle 方法会立即调用 triggerAfterCompletion,只对已经执行过 preHandle 的前几个拦截器进行清理,确保了状态的一致性。这是一个非常关键的细节,很多开发者误以为返回 false 后所有 afterCompletion 都会执行,实际上并非如此。

3.3 与 Filter 的核心区别

特性Servlet FilterHandlerInterceptor
规范/框架Servlet 规范Spring MVC 框架
作用范围由 URL Pattern 决定,范围可以很大通常与 HandlerMapping 绑定,可精确到特定的 Handler 方法
主要能力只能包装和修改请求/响应,无法直接感知具体被调用的处理器可以确切知道当前请求是哪个 Handler(Controller 方法)在处理
模型视图访问不能可以在 postHandle 中访问和修改 ModelAndView
生命周期回调initdoFilterdestroypreHandlepostHandleafterCompletion
异常处理doFilter 中可通过 try-catch 捕获后续链抛出异常afterCompletion 方法能接收到 Handler 执行时抛出的异常对象

结论:Filter 是更通用、更底层的容器级组件,而 Interceptor 是与 MVC 框架深度绑定的组件。选择时,如果需要在不触及 Spring MVC 细节的情况下做通用处理(如编码、静态资源 CORS),Filter 是首选。如果需要访问 Controller 方法、参数、返回值等 MVC 特有信息,Interceptor 更加胜任。

3.4 内联示例:请求耗时记录拦截器

// 1. 自定义拦截器: TimeLoggingInterceptor.java
@Component
public class TimeLoggingInterceptor implements HandlerInterceptor {
    private static final ThreadLocal<Long> startTime = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        startTime.set(System.currentTimeMillis());
        System.out.println("Interceptor preHandle: Request started for " + request.getRequestURI());
        return true; // 必须返回true以继续流程
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) {
        System.out.println("Interceptor postHandle: Handler executed.");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        long duration = System.currentTimeMillis() - startTime.get();
        startTime.remove(); // 防止内存泄漏
        System.out.println("Interceptor afterCompletion: Request completed in " + duration + "ms");
        if (ex != null) {
            System.out.println("Interceptor afterCompletion: An exception occured: " + ex.getMessage());
        }
    }
}

// 2. 注册拦截器: WebConfig.java
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private TimeLoggingInterceptor timeLoggingInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(timeLoggingInterceptor)
                .addPathPatterns("/api/**") // 匹配路径
                .excludePathPatterns("/api/health"); // 排除路径
    }
}

将这个概念与我们之前用 FilterRegistrationBean 注册的 Filter 结合起来,就会看到一个清晰的顺序:FirstFilterSecondFilter → (DispatcherServlet → TimeLoggingInterceptor → Controller)。通过在 FilterInterceptor 的各个回调点打印日志,可以直观地验证整个请求的完整生命周期。

4. Spring Security 过滤器链:DelegatingFilterProxy 与 FilterChainProxy

Spring Security 如何在不破坏 Servlet 规范的前提下,将其复杂的安全过滤器体系注入到 Filter Chain 中?答案就是 DelegatingFilterProxyFilterChainProxy 这两个核心桥梁。

4.1 DelegatingFilterProxy:从 Servlet 容器到 Spring IoC 的桥梁

DelegatingFilterProxy 是一个标准 Servlet Filter 的实现。它的使命不是执行安全逻辑,而是扮演一个代理,将过滤请求委托给 Spring IoC 容器中的一个 Filter Bean。

源码位置org.springframework.web.filter.DelegatingFilterProxy

public class DelegatingFilterProxy extends GenericFilterBean {

    @Nullable
    private String targetBeanName;
    // ...
    
    // 初始化时,从web.xml或FilterRegistrationBean获取targetBeanName,默认为“springSecurityFilterChain”
    public DelegatingFilterProxy(String targetBeanName) {
        this.targetBeanName = targetBeanName;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        // 懒加载获取目标Filter Bean
        Filter delegateToUse = this.delegate;
        if (delegateToUse == null) {
            synchronized (this.delegateMonitor) {
                delegateToUse = this.delegate;
                if (delegateToUse == null) {
                    // 核心:从Spring WebApplicationContext容器中查找目标Bean
                    WebApplicationContext wac = findWebApplicationContext();
                    if (wac == null) {
                        throw new IllegalStateException("No WebApplicationContext found: " +
                                "no ContextLoaderListener or DispatcherServlet registered?");
                    }
                    delegateToUse = initDelegate(wac);
                }
                this.delegate = delegateToUse;
            }
        }
        // 将实际过滤逻辑委托给从IoC容器中获取的Bean
        invokeDelegate(delegateToUse, request, response, filterChain);
    }

    // 使用ApplicationContext.getBean进行依赖查找
    protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
        String targetBeanName = getTargetBeanName();
        // 通过名称查找目标Filter Bean,强制断言为Filter类型
        Filter delegate = wac.getBean(targetBeanName, Filter.class);
        if (isTargetFilterLifecycle()) {
            delegate.init(getFilterConfig()); // 如果目标Filter也需要初始化,则调用其init方法
        }
        return delegate;
    }

    protected void invokeDelegate(Filter delegate, ServletRequest request, 
                                  ServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        // 直接调用目标Filter的doFilter方法,将原始的FilterChain传递下去
        delegate.doFilter(request, response, chain);
    }
    
    // ...其余代码
}

设计意图深度解读

  • 懒加载与缓存DelegatingFilterProxy 并不是在初始化时就去查找目标 Bean,而是在第一次请求到来时才进行查找(initDelegate),并通过 volatile 结合 synchronized 的方式(DCL)缓存获取到的 Bean 实例。这样做是为了解决容器启动时,WebApplicationContext 可能尚未完全初始化好的时序问题。
  • IoC 依赖查找:源代码中 wac.getBean(targetBeanName, Filter.class) 是核心。它使用了 IoC 容器的依赖查找能力。这意味着,springSecurityFilterChain 这个 Bean 及其内部所有的安全过滤器,都可以在 Spring 容器中享受完整的生命周期管理、依赖注入、AOP 等高级特性。这是 Spring Security 强大可扩展性的根基
  • 委托模式:其设计完美体现了委托模式。它本身是一个标准 Filter,满足了 Servlet 容器的要求,但将所有实际工作委托给了另一个对象。这使得 Spring Security 的过滤器链对 Servlet 容器来说是透明的。
  • 桥梁作用:它将 Servlet 规范的生命周期(初始化、销毁、过滤)与 Spring IoC 容器的生命周期完美桥接。

为什么需要它? 因为像 FilterSecurityInterceptor 这样的安全过滤器,需要注入 AuthenticationManagerAccessDecisionManager 等复杂的依赖。如果直接将其注册为 Servlet Filter,它只是一个由容器实例化的普通对象,无法享受 DI。DelegatingFilterProxy 解决了这个根本矛盾。

4.2 FilterChainProxy:安全过滤器链的管理器

DelegatingFilterProxy 将请求委托给名为 springSecurityFilterChain 的 Bean 时,这个 Bean 的实际类型便是 FilterChainProxy。它是 Spring Security 过滤器链的入口点和管理核心。

源码位置org.springframework.security.web.FilterChainProxy

public class FilterChainProxy extends GenericFilterBean {
    // 持有多个SecurityFilterChain实例
    private List<SecurityFilterChain> filterChains;
    
    // ...构造函数和初始化方法

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
        if (clearContext) {
            // 1. 确保当前请求只被Security过滤器链处理一次
            request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
            try {
                // 2. 核心:调用内部私有方法,执行实际的过滤器链匹配和调用逻辑
                doFilterInternal(request, response, chain);
            } finally {
                // 3. 在所有安全过滤器执行完毕后,清除SecurityContextHolder中的上下文,防止内存泄漏
                SecurityContextHolder.clearContext();
                request.removeAttribute(FILTER_APPLIED);
            }
        } else {
            // 当前请求已被处理,直接调用原始FilterChain的下一个Filter
            chain.doFilter(request, response);
        }
    }

    private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        FirewalledRequest fwRequest = firewall.getFirewalledRequest((HttpServletRequest) request);
        HttpServletResponse fwResponse = ((HttpServletResponse) response);
        
        // 3.1 遍历所有SecurityFilterChain,找到第一个匹配的
        List<Filter> filters = getFilters(fwRequest);
        if (filters == null || filters.size() == 0) {
            // ...日志...
            fwRequest.reset();
            chain.doFilter(request, response); // 若无匹配的安全过滤器,直接放行
            return;
        }
        // 3.2 为找到的SecurityFilterChain创建一个虚拟的FilterChain并执行
        VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
        vfc.doFilter(fwRequest, fwResponse);
    }

    // 核心匹配逻辑:遍历filterChains,调用每个SecurityFilterChain的matches方法
    private List<Filter> getFilters(HttpServletRequest request) {
        for (SecurityFilterChain chain : filterChains) {
            // SecurityFilterChain的matches方法决定了该链是否适用于当前请求
            if (chain.matches(request)) {
                return chain.getFilters();
            }
        }
        return null;
    }

    // 内部类:虚拟过滤器链,负责遍历和调用单个SecurityFilterChain内的Filter列表
    private static class VirtualFilterChain implements FilterChain {
        private final FirewalledRequest firewalledRequest;
        private final FilterChain originalChain;
        private final List<Filter> additionalFilters;
        private final int size;
        private int currentPosition = 0;

        // 构造函数...

        @Override
        public void doFilter(ServletRequest request, ServletResponse response) 
                throws IOException, ServletException {
            if (currentPosition == size) {
                // 安全过滤器链执行完毕,将请求交还给原始的Servlet Filter Chain
                if (logger.isDebugEnabled()) {
                    logger.debug(UrlUtils.buildRequestUrl(firewalledRequest) 
                                 + " reached end of additional filter chain; proceeding with original chain");
                }
                firewalledRequest.reset();
                originalChain.doFilter(request, response);
            } else {
                currentPosition++;
                // 获取下一个安全过滤器并执行
                Filter nextFilter = additionalFilters.get(currentPosition - 1);
                nextFilter.doFilter(request, response, this);
            }
        }
    }
}

设计意图深度解读

  • 多安全链支持FilterChainProxy 持有的 List<SecurityFilterChain> 是其最强大的设计之一。这使得我们可以为应用不同部分配置完全独立的安全策略。例如,/api/** 使用 JWT 认证链,/web/** 使用表单登录 + Session 链,而 /actuator/** 使用基本认证链。这种设计极大地提升了系统安全架构的灵活性。
  • 责任链模式的双重嵌套
    1. 外层FilterChainProxy 本身是 Servlet FilterChain 中的一个节点。
    2. 内层FilterChainProxy 内部通过 VirtualFilterChain 执行选定的 SecurityFilterChain 中的一系列安全 Filter。 这就是一个“链中链”的结构,是责任链模式的复合应用。
  • 惰性匹配getFilters 通过遍历 filterChains,调用 SecurityFilterChain.matches(request) 来决定使用哪套过滤器。匹配成功后就立即返回,这意味着多个 SecurityFilterChain 的顺序至关重要,必须将范围最具体的链放在最前面,范围最泛化的链放在最后面(如 /**
  • 上下文清理FilterChainProxyfinally 块中执行 SecurityContextHolder.clearContext(),这是防止内存泄漏的关键措施。它确保安全上下文(如用户认证信息)只在当前请求的线程内有效,请求结束后立即清理,不会污染 Tomcat 线程池中的其它请求。
sequenceDiagram
    participant C as 客户端
    participant SC as Servlet容器
    participant OF as 其他Filter
    participant DFP as DelegatingFilterProxy
    participant IOC as Spring IoC容器
    participant FCP as FilterChainProxy
    participant SFC as SecurityFilterChain
    participant FF1 as Security Filter 1
    participant FF2 as Security Filter 2

    C->>SC: HTTP Request
    SC->>OF: filter(chain)
    OF->>DFP: chain.doFilter()
    
    Note over DFP, IOC: 1. 桥接阶段
    alt 首次调用
        DFP->>IOC: getBean("springSecurityFilterChain")
        IOC-->>DFP: FilterChainProxy实例
        DFP->>DFP: 缓存delegate
    end
    DFP->>FCP: delegate.doFilter(req, res, chain)
    
    Note over FCP, SFC: 2. 安全链匹配阶段
    FCP->>FCP: getFilters(request)
    loop 遍历filterChains
        FCP->>SFC: chain.matches(request)
        alt 匹配成功
            SFC-->>FCP: true
            FCP->>FCP: 返回chain.getFilters()
        else 匹配失败
            SFC-->>FCP: false
        end
    end

    Note over FCP, FF2: 3. 安全过滤器链执行阶段
    FCP->>FCP: new VirtualFilterChain(filters)
    FCP->>FF1: vfc.doFilter(req, res)
    FF1->>FF2: vfc.doFilter(req, res)
    FF2->>FF1: vfc.doFilter(req, res)
    Note over FF1, FCP: 安全过滤链执行完毕
    FCP->>OF: originalChain.doFilter(req, res)
    OF->>SC: chain.doFilter()
    SC-->>C: 响应

图表主旨概括:该序列图展示了 DelegatingFilterProxy 如何作为代理,从 Spring IoC 容器中获取 FilterChainProxy 实例,并将请求委托给它。接着展示了 FilterChainProxy 如何管理多个 SecurityFilterChain,匹配后将请求交由内部的虚拟过滤器链 VirtualFilterChain 执行。

逐层/逐元素分解

  • 客户端/容器:发起和接收 HTTP 请求的起点和终点。
  • 其他 Filter:代表了在 DelegatingFilterProxy 上下文中运行的其他 Servlet Filter。
  • DelegatingFilterProxy (DFP):核心桥梁。它拦截请求,通过 getBean 从 IoC 容器中查找名为 springSecurityFilterChain 的 Bean,并将实际过滤逻辑全部委托给它。
  • Spring IoC 容器:持有 Spring Security 完整过滤体系的 Bean 工厂。
  • FilterChainProxy (FCP):安全链的入口点和管理器。它持有一个 SecurityFilterChain 列表,并通过匹配请求找到合适的一条子链。
  • SecurityFilterChain (SFC):一组安全过滤器的集合,包含一个 matches 方法用于声明其能处理的请求模式。
  • VirtualFilterChain (VFC):由 FilterChainProxy 创建的内部对象,用于驱动选定的 SecurityFilterChain 中各个 Filter 的顺序执行。

设计原理映射

  • 代理模式DelegatingFilterProxy 是标准 Servlet Filter 的代理,将功能委托给 Spring Bean。
  • 责任链模式:整个流程是责任链的嵌套组合。外层的 Servlet Filter Chain,中间的 Spring Security Filter Chain(由 FilterChainProxy 代表),以及内部的 VirtualFilterChain,都是责任链模式的具体实现。
  • 策略模式SecurityFilterChain 接口相当于一个策略,其 matches 方法决定了该策略是否适用于当前请求上下文。FilterChainProxy 根据此策略动态选择执行哪一组过滤器。

工程联系与关键结论

  • 工程联系:调试 Spring Security 问题时,可以在 DelegatingFilterProxy.invokeDelegateFilterChainProxy.doFilterInternal 方法入口设置断点,这是区分请求卡在“从容器到 Spring 的桥上”还是在“Spring Security 链内部”的关键分界点。
  • 关键结论DelegatingFilterProxy 解决了 Servlet Filter 无法享受 DI 的难题,而 FilterChainProxy 则为应用提供了声明式、可匹配、模块化的安全策略配置能力。理解这一委托-匹配-执行三部曲,是掌握 Spring Security 体系结构的核心。

5. 核心安全过滤器序列与职责详解

Spring Security 的威力在于其内部的过滤器链。理解这些过滤器的顺序和职责是进行安全定制的关键。SecurityFilterChain 决定了它们的顺序。

sequenceDiagram
    autonumber
    participant VFC as VirtualFilterChain
    participant SCPF as SecurityContextPersistenceFilter
    participant UPAF as UsernamePasswordAuthenticationFilter
    participant ETF as ExceptionTranslationFilter
    participant FSI as FilterSecurityInterceptor
    participant OC as Original Chain

    VFC->>+SCPF: doFilter(req, res)
    Note over SCPF: 1. 从HttpSession加载SecurityContext到SecurityContextHolder
    SCPF->>VFC: chain.doFilter()
    VFC->>+UPAF: doFilter(req, res)
    Note over UPAF: 2. 尝试从请求中提取认证信息,创建Authentication
    UPAF->>VFC: chain.doFilter()
    VFC->>+ETF: doFilter(req, res)
    Note over ETF: 3. 使用try-catch包装后续调用,捕获认证与授权异常
    ETF->>VFC: chain.doFilter()
    VFC->>+FSI: doFilter(req, res)
    Note over FSI: 4. 从SecurityContextHolder获取认证,调用AccessDecisionManager进行授权决策
    alt 授权失败
        FSI-->>ETF: 抛出AccessDeniedException
    else 授权成功
        FSI->>VFC: chain.doFilter()
        Note over VFC, OC: 所有安全过滤器执行完毕,交还控制权至原始链
    end
    Note over ETF: 捕获到AccessDeniedException,启动AccessDeniedHandler处理
    ETF-->>VFC: 异常处理完成,不向上抛
    VFC-->>SCPF: 请求从安全链返回
    Note over SCPF: 5. 清理SecurityContext, 并将最新状态持久化回HttpSession

图表主旨概括:此序列图展示了 Spring Security 中四个核心过滤器的协作流程与顺序,清晰呈现了从上下文恢复、认证、异常捕获到最终授权决策的完整安全链路。

逐层/逐元素分解

  • SecurityContextPersistenceFilter:位于链的最前端和最后端。请求进入时,它从 HttpSession 恢复 SecurityContext 并放入 SecurityContextHolder;请求返回时,它清理 SecurityContextHolder 并将更新后的 Context 持久化到 Session。
  • UsernamePasswordAuthenticationFilter:处理基于表单的登录认证。当它检测到登录请求(如 /login, POST)时,提取用户名和密码,创建 UsernamePasswordAuthenticationToken,并调用 AuthenticationManager 进行验证。
  • ExceptionTranslationFilter:一个关键的异常处理过滤器。它位于 FilterSecurityInterceptor 之上,负责捕获其抛出的 AuthenticationExceptionAccessDeniedException,并调用相应的入口点(如重定向到登录页)或拒绝处理器(如返回 403)。
  • FilterSecurityInterceptor:授权决策的最终执行者。它从 SecurityContextHolder 获取当前认证信息,根据配置的安全元数据(如角色、权限)和投票器 (AccessDecisionManager),决定是否允许访问。

设计原理映射

  • 模板方法/拦截过滤器:每个 Filter 都遵循“前置处理 -> chain.doFilter() -> 后置处理”的模式。SecurityContextPersistenceFilter 典型地应用了此模式,在请求前后管理 Session。
  • 面向切面编程 (AOP) 思想的体现:ExceptionTranslationFilter 将异常处理逻辑从业务流程中剥离,作为一个横切关注点独立出来,这与 AOP 的理念不谋而合。
  • 策略模式FilterSecurityInterceptor 调用的 AccessDecisionManager 采用策略模式,允许通过不同的投票策略(AffirmativeBasedConsensusBasedUnanimousBased)来决定是否授权。

工程联系与关键结论

  • 工程联系:如果你需要实现一个“记住我”或 Token 自动登录功能,自定义的过滤器应该放在 UsernamePasswordAuthenticationFilter 之前,以便在表单登录尝试之前完成认证。如果你需要审计所有受保护资源的访问日志,自定义过滤器应放在 FilterSecurityInterceptor 之后或整合到其中。
  • 关键结论Spring Security 内置过滤器的顺序是精心设计的,不可随意调整。SecurityContextPersistenceFilter 的上下文恢复、ExceptionTranslationFilter 的异常隔离、FilterSecurityInterceptor 的最终裁决,共同构成了一个稳固的‘认证-授权-异常处理’闭环。

6. 三者执行顺序与协作全景

至此,我们已经分别剖析了三层拦截机制。现在,将它们放在一条时间线上,观察一个完整请求的全生命周期。

sequenceDiagram
    actor Client
    participant Container as Servlet容器(Tomcat)
    participant CustomF as 自定义Filter
    participant DFP as DelegatingFilterProxy
    participant FCP as FilterChainProxy(Spring Security)
    participant SecF as 安全过滤器序列
    participant Dispatcher as DispatcherServlet
    participant HandlerMap as HandlerMapping
    participant Interceptor as HandlerInterceptor
    participant Controller as Controller方法

    Client->>Container: HTTP Request
    Container->>CustomF: 1. doFilter(request, response)
    CustomF->>CustomF: 前置逻辑
    CustomF->>DFP: chain.doFilter()
    DFP->>FCP: 2. delegate.doFilter()
    FCP->>FCP: 匹配SecurityFilterChain
    FCP->>SecF: 3. VirtualFilterChain.doFilter()
    loop 安全过滤器逐个执行
        SecF->>SecF: SecurityContextFilter/UPAF/ETF/FSI...
    end
    SecF-->>FCP: 安全链执行完毕
    FCP-->>DFP: 返回
    DFP-->>CustomF: 返回
    CustomF->>CustomF: 后置逻辑
    CustomF-->>Container: 返回
    
    Container->>Dispatcher: 4. service(request, response)
    Dispatcher->>HandlerMap: 5. getHandler(request)
    HandlerMap-->>Dispatcher: HandlerExecutionChain
    Dispatcher->>Interceptor: 6. applyPreHandle(request, response, handler)
    Interceptor-->>Dispatcher: true
    Dispatcher->>Controller: 7. handler.handle(request, response)
    Controller-->>Dispatcher: ModelAndView
    Dispatcher->>Interceptor: 8. applyPostHandle(request, response, mav)
    Interceptor-->>Dispatcher: void
    Dispatcher->>Dispatcher: 9. 视图渲染
    Dispatcher->>Interceptor: 10. triggerAfterCompletion(request, response, ex)
    Interceptor-->>Dispatcher: void
    Dispatcher-->>Container: 响应
    Container-->>Client: HTTP Response

图表主旨概括:此泳道图以时间为轴,严格按序展示了从请求进入 Servlet 容器,经由自定义 Filter、Spring Security 代理、安全过滤器链,再进入 DispatcherServletHandlerInterceptor,最终抵达 Controller 并返程的完整路径。

逐层/逐元素分解

  • 自定义 Filter:运行于 Servlet 容器管理的 Filter Chain 中,包裹了 DelegatingFilterProxy
  • DelegatingFilterProxy & FilterChainProxy:作为桥梁,将请求从 Servlet Filter Chain 路由到 Spring Security 的滤镜序列上。
  • 安全过滤器序列:执行认证、授权等核心安全任务。在执行期间,DispatcherServlet 尚未接收到请求。
  • DispatcherServlet & HandlerInterceptor:在安全过滤器链完全放行后,请求才到达 DispatcherServlet。拦截器的 preHandle 在 Handler 调用前执行,postHandle 在 Handler 调用后、视图渲染前执行,afterCompletion 在视图渲染后执行。

设计原理映射

  • Decorator/Chain of Responsibility 的复合:整个流程是装饰器和责任链模式的复合体。外层的 Filter 链像一个层层嵌套的洋葱,每一层都可以增加自己的行为。Spring Security 通过代理成功地作为一层特殊的“装饰”嵌入了这个体系。
  • 关注点分离:每一层都处理各自的关注点。自定义 Filter 处理加解密、日志;Spring Security Filter 处理认证授权;Interceptor 处理 Controller 级别的执行信息。这种清晰的分层是优秀架构的标志。

工程联系与关键结论

  • 跨层异常传播
    • Filter 中抛出的未捕获异常,如果是安全链之前,将由 Servlet 容器处理;如果在安全链之后,可能被 DispatcherServlet 的错误页面机制接管。
    • 关键点:Spring Security 的 ExceptionTranslationFilter 会截获其后的 FilterSecurityInterceptor 等抛出的 AccessDeniedException,并将其转换为 HTTP 302(重定向到登录)或 403 响应。这意味着,如果 Spring Security 进行了访问拒绝,请求会在此处中断,自定义 Filter 的后置逻辑依然会执行(因为它在栈外层),但所有的 HandlerInterceptor 都不会再执行。
    • Controller 中抛出的异常会被 DispatcherServlet 的异常解析机制处理,并触发 afterCompletion,但对已经在返回路径上的 Filter 无影响,因为它们在调用链的栈上。
  • 关键结论深刻理解 Filter、DispatcherServlet、Interceptor 三者的栈式执行顺序和异常传播边界,是解决诸如为什么自定义日志没打印,为什么 Response Header 没加上,为什么全局异常捕获器失效等问题的不二法门。请求的‘进去’和‘出来’路径是镜像对称的,而异常是这条对称路径上的‘短路’信号。

7. 生产事故排查专题

理论的价值在于指导实践。以下通过两个生产事故,反向印证三层拦截协作机制的重要性。

事故 1:幽灵般的“请求被记录两次”

  • 现象:运维监控系统显示,业务高峰期时,某些核心 API 的访问日志会重复记录,导致部分接口的 QPS 统计数据翻倍,触发错误的扩容警报。日志显示两条记录的时间戳非常接近(相差 1-2ms),但 ThreadId 相同,同一个 HTTP 请求被日志 Filter 处理了两次。

  • 排查思路

    1. 确认网络层:检查上游 Nginx、API 网关的日志,确认它们只向应用实例发送了一次请求。排除网络重放。
    2. 锁定日志 Filter:日志记录在一个自定义的 LoggingFilter 中。在该 Filter 的 doFilter 入口和出口处设置断点。
    3. 分析调用栈:第一个断点被触发时,调用栈是正常的:Tomcat -> FilterChain -> LoggingFilter
    4. 发现幽灵调用:紧接着,第二个断点被触发了。查看调用栈,发现第二次进入 LoggingFilter 的调用栈竟然是从 LoggingFilter 自身往下,穿过了一堆 Spring Security 的 Filter,然后再次回到了 TomcatFilterChain。调用栈显示同一个请求在 Filter 链中循环了。
  • 根因分析: 检查 Filter 注册代码,发现了问题所在。

    // 错误配置 1:类上使用了@WebFilter注解
    @WebFilter(urlPatterns = "/*", filterName = "loggingFilter")
    public class LoggingFilter implements Filter {
        // ...
    }
    
    // 错误配置 2:同时又在配置类中通过FilterRegistrationBean注册了一次
    @Configuration
    public class FilterConfig {
        @Bean
        public FilterRegistrationBean<LoggingFilter> loggingFilterRegistration() {
            FilterRegistrationBean<LoggingFilter> bean = new FilterRegistrationBean<>(new LoggingFilter());
            bean.setUrlPatterns(Collections.singleton("/*"));
            bean.setOrder(Ordered.HIGHEST_PRECEDENCE + 100);
            return bean;
        }
    }
    

    Spring Boot 自动配置会扫描 @WebFilter 注解并将该 Filter 注册到 Servlet 容器一次(通常排在较前)。同时,我们又通过 FilterRegistrationBean 显式注册了同一个 Filter 的另一个新实例,并指定了 Order。这导致在 Tomcat 的 ApplicationFilterChain 中,同一个逻辑的 Filter 被注册了两次。一个请求经过第一个 LoggingFilter 实例后,穿过 Spring Security 的代理,最后又经过第二个 LoggingFilter 实例,导致日志打印两次。

  • 解决方案: 移除 @WebFilter 注解,只保留 FilterRegistrationBean 的注册方式。

    // @WebFilter 注解已删除
    public class LoggingFilter implements Filter { ... }
    

    或者,如果 Spring Boot 启动了自动扫描,可以通过在主类上添加 @ServletComponentScan 来控制扫描范围,或通过 @Bean 方式集中管理所有 Filter,完全放弃 @WebFilter最佳实践是对于需要严格控制顺序的 Filter,统一使用 FilterRegistrationBean

  • 最佳实践对于需要严格控制顺序和依赖注入的 Servlet 组件,应避免混用 @WebFilterFilterRegistrationBean。统一使用 FilterRegistrationBean 进行集中配置管理,可以有效避免因重复注册导致的幽灵请求、顺序混乱等问题。

事故 2:间歇性“失忆”的安全上下文

  • 现象:在用户量增大的时段,有少量用户报告在已登录状态下,访问某些页面时会被当作用户未登录,跳转到登录页。问题出现没有规律,频率低但影响恶劣,在单个用户操作时偶尔出现,程序无报错。

  • 排查思路

    1. 分析安全上下文:问题表现为“失忆”,即 SecurityContextHolder.getContext().getAuthentication()null。这很可能是在某个环节上下文被错误清除了。
    2. 检查核心过滤器:重点审查 SecurityContextPersistenceFilter 和任何在安全链中运行的自定义 Filter。
    3. 审查自定义 Filter:发现有一个自定义的 RequestValidationFilter,用于校验请求中的某个动态 Token。它被注册在 FilterChainProxy 之前(通过 FilterRegistrationBean 指定 order 在 springSecurityFilterChain 之前)。
    public class RequestValidationFilter implements Filter {
        @Override
        public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 
                throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) req;
            // ... 校验逻辑 ...
            try {
                chain.doFilter(request, res); // 正常放行
            } finally {
                // 致命的错误:在 finally 块中粗暴地清空了所有上下文
                // 本想清理自己Filter中可能产生的临时数据,结果“误伤”了安全上下文
                SecurityContextHolder.clearContext();
            }
        }
    }
    

    这个 Filter 在 finally 块中调用了 SecurityContextHolder.clearContext()。由于它在 Spring Security 过滤器链的外层,当请求成功通过安全链(此时 Context 已被设置)并从这个自定义 Filter 返回时,它的 finally 块执行了“清空”操作,将 Spring Security 刚设好的认证信息给抹除了。这就是“失忆”的根源。之所以偶发,是因为此 Filter 可能只对某些异步处理或特定路径的请求进行校验,且并发场景下线程复用时若清理不当也可能导致后续请求上下文状态异常。

  • 根因分析: 错误地跨层管理了 SecurityContextHolderSecurityContextHolder 的清理是 Spring Security 的责任,具体由 FilterChainProxyfinally 块和 SecurityContextPersistenceFilter 协作完成。位于外层的业务 Filter 不应该、也无权直接操作 SecurityContextHolder。这种粗暴的清理,打破了 Spring Security 维护的线程安全边界。

  • 解决方案: 移除自定义 Filter 中的 SecurityContextHolder.clearContext() 调用。如果确实需要存储临时数据,应使用 HttpServletRequest.setAttribute 或自定义的 ThreadLocal 进行管理,并保证其清理逻辑仅限于自己管理的对象,绝不触碰 SecurityContextHolder

  • 最佳实践在 Filter 栈中,所有位于 DelegatingFilterProxy 之外的业务 Filter,要绝对避免直接读取或修改 SecurityContextHolder。这是 Spring Security 内部管理的私有状态。任何跨层的上下文管理都将是故障的温床。对于请求级的临时状态,应使用 HttpServletRequest.setAttribute/getAttribute

8. 面试高频专题

1. Spring 中有哪些拦截请求的方式?区别是什么?

  • 回答:主要有两种:Servlet 规范中的 Filter 和 Spring MVC 框架中的 HandlerInterceptor。本质上 Spring Security 的过滤器链也是通过对 Filter 的代理实现的。
    • Filter 是 Servlet 容器管理的,作用在请求进入 DispatcherServlet 之前,可以修改请求和响应,但无法感知具体执行的 Handler。
    • Interceptor 是 Spring MVC 管理的,作用在 DispatcherServlet 和 Controller 之间,可以精确知道调用的 Handler,并能访问 ModelAndView
  • 追问 1:Spring Security 算在哪一层?
    • 它本质上是 Filter 层,通过 DelegatingFilterProxy 将一个 Spring 管理的 Bean 作为一个 Filter 插入到 Filter Chain 中。
  • 追问 2:为什么不用一个大的 Interceptor 做所有安全控制?
    • 因为安全有时需要在请求到达 Spring 容器之前就拦截,例如防止某些类型的攻击(如路径遍历),或为静态资源提供安全保护,这些都不是 Interceptor 能做到的。Filter 更靠近请求入口。
  • 追问 3:如果我想在 Filter 中访问 Spring Bean 怎么办?
    • 要么通过 DelegatingFilterProxy 将你的 Filter 也声明为 Spring Bean,要么使用 SpringBeanAutowiringSupport 或在 init 中通过 WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()).getBean(...) 获取,但最佳实践是前者。
  • 加分回答:可以提到 Spring WebFlux 中的 WebFilter,它构成了 Reactive 栈的请求拦截方式,与 Servlet 栈的 Filter 截然不同,体现了 Reactive 流式处理的特点。

2. Filter 和 HandlerInterceptor 的区别?各自适合什么场景?

  • 回答:(引用上文表格进行阐述,强调生命周期、作用范围和能力的不同)。
    • 场景选择:编码、CORS、安全、日志记录框架(如 MDC)、请求体包装等通用、底层任务,适合用 Filter。记录 Controller 方法耗时、对特定 Handler 返回值做统一增强、对某些特定注解做 AOP 等与业务 Handler 紧密耦合的任务,适合用 Interceptor。
  • 追问 1afterCompletion 和 Filter 的 finally 块有什么区别?
    • afterCompletion 是在 DispatcherServlet 视图渲染之后执行,此时响应流已经被提交或即将被提交。Filter 的 finally 块是在 chain.doFilter 返回后立即执行,此时响应可能刚离开 Filter 链,还处于完全可控状态。
  • 追问 2:Interceptor 的 preHandle 返回 false 后会发生什么?
    • HandlerExecutionChain.applyPreHandle 会停止遍历,并立即触发已经执行过 preHandle 的拦截器的 afterCompletion 方法,之后整个请求处理中止,后续拦截器和 Handler 都不会执行。
  • 追问 3:Filter 如何做到类似 preHandle 返回 false 的效果?
    • 在 Filter 的 doFilter 方法中,不调用 chain.doFilter(request, response) 即可。这样请求链条就被中断了,但必须自己处理向客户端返回响应。
  • 加分回答:可以提到 OncePerRequestFilter,它是解决 Filter 在异步分发或 forward/include 时被多次调用的最佳实践,而 Interceptor 不存在这种问题,因为它的生命周期由一次 DispatcherServlet 调度决定。

3. DelegatingFilterProxy 的作用和工作原理?

  • 回答:它是一个标准 Servlet Filter 的代理,目的是将过滤请求委托给 Spring IoC 容器中管理的一个 Filter Bean(默认名为 springSecurityFilterChain)。它解决了 Servlet 容器直接实例化的 Filter 无法享受依赖注入的问题。工作原理是在第一次请求时,通过 ApplicationContext.getBean 懒加载获取目标 Bean,并缓存起来,此后所有请求都委托给这个 Bean 处理。
  • 追问 1:为什么要用延迟加载?
    • 因为 DelegatingFilterProxy 作为一个标准 Filter 被容器初始化时,Spring 的根上下文或 Servlet 上下文可能还没有完全启动,立即查找 Bean 会失败。延迟加载可以确保在第一个请求到来时,Spring 上下文已经就绪。
  • 追问 2:能否代理目标 Bean 的生命周期?
    • 可以。通过设置 targetFilterLifecycletrueDelegatingFilterProxy 会让目标 Bean 也遵循 Servlet Filter 的生命周期,调用其 initdestroy 方法。
  • 追问 3GenericFilterBeanDelegatingFilterProxy 的关系?
    • DelegatingFilterProxy 继承自 GenericFilterBean,而 GenericFilterBean 是 Spring 为了便于将 Filter 和 Spring 配置整合而设计的一个基类,允许通过初始化参数(<init-param>)方便地设置属性。这是一种模板方法模式的体现。
  • 加分回答:可以类比理解,它的作用就像 JNDI 的 ObjectFactory 或 Service Locator 模式,将资源的查找(Filter Bean)与资源的使用(doFilter)解耦。

4. FilterChainProxy 如何管理多个 SecurityFilterChain?

  • 回答FilterChainProxy 内部持有 List<SecurityFilterChain>。当请求到达时,它按顺序遍历这个列表,并调用每个 SecurityFilterChainmatches(request) 方法。一旦找到第一个匹配的 SecurityFilterChain,它就取出该链对应的 List<Filter>,并用一个内部类 VirtualFilterChain 来顺序执行这些 Filter。
  • 追问 1:如果有多个链都匹配了同一个请求怎么办?
    • 只有列表中第一个匹配的链会被执行。因此,SecurityFilterChain 的顺序至关重要,必须将最具体、最优先的链配置在列表的最前面。
  • 追问 2SecurityFilterChainmatches 方法是怎样实现的?
    • 最常见的实现是 DefaultSecurityFilterChain,它使用 RequestMatcher 来进行匹配。例如 AntPathRequestMatcherRegexRequestMatcher。一个链可以配置零个或者多个 RequestMatcher,只有所有匹配器都匹配时 matches 才返回 true。如果没配置任何匹配器,则匹配所有请求。
  • 追问 3:如果一个 SecurityFilterChain 也没有匹配到会怎样?
    • FilterChainProxy.getFilters 会返回 null,此时 FilterChainProxy 会直接调用 chain.doFilter 将请求交给 Servlet Filter Chain 的下一个组件,相当于没有任何安全过滤。
  • 加分回答FilterChainProxy 的这种设计是一种典型的策略链模式在安全领域的应用,每个 SecurityFilterChain 都是一个具体的认证/授权策略集合,FilterChainProxy 根据请求特征自动分派,实现了极高的灵活性和扩展性。

5. Spring Security 默认的过滤器有哪些?它们的顺序可以改变吗?

  • 回答:主要的核心过滤器及其默认顺序为:ChannelProcessingFilter (http/https) -> SecurityContextPersistenceFilter -> ConcurrentSessionFilter -> UsernamePasswordAuthenticationFilter -> ...-> ExceptionTranslationFilter -> FilterSecurityInterceptor。顺序可以通过配置改变,但强烈不建议随意调整。
  • 追问 1:如果要添加一个自定义的认证 Filter,应该放在哪里?
    • 原则上应放在 UsernamePasswordAuthenticationFilter 附近或之前,并在其中设置 AuthenticationSecurityContextHolder。具体位置取决于你想让它先于还是后于表单登录执行。
  • 追问 2:为什么要让 ExceptionTranslationFilter 放在 FilterSecurityInterceptor 前面?
    • 因为 ExceptionTranslationFilter 的目的是通过 try-catch 捕获其后的 Filter 抛出的异常。如果它在 FilterSecurityInterceptor 之后,就无法捕获授权失败异常,核心异常处理机制就失效了。
  • 追问 3:如何改变顺序?
    • 通过继承 WebSecurityConfigurerAdapter,重写 configure(HttpSecurity http),可以使用 http.addFilterBefore(filter, class)http.addFilterAfter(filter, class) 方法来将自定义 Filter 添加到指定内置过滤器之前或之后。
  • 加分回答:深层原理是 HttpSecurity 维护了一个 filters 的有序列表,addFilterBefore 等方法在内部通过比较器(FilterOrderRegistration)和映射关系,计算出自定义 Filter 应该插入的正确索引位置。

6. 如果自定义了一个 Filter,如何调整它的执行顺序?

  • 回答:分两种情况:
    • 如果注册为普通的 Servlet Filter:使用 Spring Boot 的 FilterRegistrationBean 并通过 setOrder(int) 方法,或让 Filter 实现 OrderedPriorityOrdered 接口。数值越小,优先级越高,越先执行。
    • 如果注册为 Spring Security 的安全 Filter:在 WebSecurityConfigurerAdapter.configure(HttpSecurity http) 中使用 http.addFilterBefore(...)http.addFilterAfter(...)http.addFilterAt(...) 来精确指定相对于其它安全 Filter 的顺序。
  • 追问 1@Order 注解对 Spring Security Filter 生效吗?
    • 如果一个 Bean 被注册为普通 Servlet Filter,@Order 会生效。但如果它被注册到 Spring Security 的 HttpSecurity 中,其顺序由 HttpSecurityaddFilter 系列方法决定,@Order 注解对它无效。
  • 追问 2FilterRegistrationBean.setOrder(1)@Order(1) 哪个优先级更高?
    • FilterRegistrationBean.setOrder() 的优先级高于 @Order 注解。如果两者都存在,setOrder() 返回的值是最终决定顺序的依据。
  • 追问 3:如果我把自定义的安全 Filter 注入为 Bean,并用 FilterRegistrationBean 注册,会发生什么?
    • 这会造成该 Filter 既作为普通 Filter 执行一次,又作为 Spring Security Filter 执行一次。这通常是错误配置,会导致该 Filter 的逻辑被执行两遍,极力避免。
  • 加分回答:理解 OrderedFilter 在 Spring Boot 自动配置中的应用,例如 CharacterEncodingFilter 使用 Ordered.HIGHEST_PRECEDENCE 确保其最先执行。

7. 为什么 Spring Security 需要 DelegatingFilterProxy,而不是直接注册一个普通的 Filter?

  • 回答:根本原因是为了解决依赖注入的问题。Spring Security 的过滤器链组件(如 FilterSecurityInterceptor)依赖于 AuthenticationManagerAccessDecisionManager 等复杂的 Bean。如果直接注册为普通 Filter,它将由 Servlet 容器通过 new 关键字实例化,无法成为一个 Spring Bean,也就无法享受 DI、AOP 和完整的生命周期管理。DelegatingFilterProxy 充当了一个桥梁,它本身是一个轻量级的标准 Filter,但能将核心工作委托给从 IoC 容器中获取的 Bean。
  • 追问 1:如果把 Spring Security 的所有 Bean 都通过某种方式注入到一个普通的 Filter 里,可以吗?
    • 技术上很复杂但理论上可行,不过这违背了框架设计原则。它会导致安全组件与 Servlet 容器紧密耦合,无法复用 Spring 的生命周期管理、测试支持和扩展机制。DelegatingFilterProxy 是一种优雅的解耦方案。
  • 追问 2:除了代理,DelegatingFilterProxy 还解决了什么其他问题?
    • 延时加载,解决了启动时序问题。同时也提供了目标 Filter 生命周期管理的可选桥接。
  • 追问 3:如果没有 DelegatingFilterProxy,FilterChainProxy 能否直接作为一个 Filter 运行?
    • 可以,但前提是 Servlet 容器有能力在创建 FilterChainProxy 实例时注入其所有依赖,而标准的 Servlet 容器规范不支持这种机制。
  • 加分回答:这种“代理 + 委托 + IoC 查找”的模式在 Spring 生态中非常常见,例如 MethodValidationPostProcessor 也是通过代理 Bean 并委托给校验器来工作的,其核心思想都是将特定领域的逻辑与基础框架解耦。

8. 一次请求中,Filter、Spring Security 的 Filter、Interceptor 的执行顺序是怎样的?

  • 回答:(参照第6部分和泳道图进行回答,清晰描述:外层 Filter -> Spring Security Filter Chain -> DispatcherServlet -> HandlerInterceptor -> Controller -> 反向返回)
  • 追问 1:如果 Spring Security 的 ExceptionTranslationFilter 捕获并处理了异常,后续的自定义 Filter 的 afterCompletion 还会执行吗?
    • Filter 没有 afterCompletion,有 doFilter 方法返回后的代码(类似于 afterCompletion)。ExceptionTranslationFilter 在安全链内部捕获了异常,它会使安全链正常返回(不向上层抛异常)。所以,包装在 Spring Security 之外的外层 Filter 在 chain.doFilter() 之后的代码依然会执行,而位于其内部的 Interceptor 的 afterCompletion 不会执行,因为请求根本没到 DispatcherServlet。
  • 追问 2:我在 Controller 中抛了个异常,我的自定义日志 Filter 还能记录到完整的请求/响应吗?
    • 可以。只要这个 Filter 是包装在 DispatcherServlet 之外的,在 doFilter 调用返回后,请求和响应对象都已经历了完整的处理过程,你的 Filter 仍可以读取和处理它们。
  • 追问 3:请求的目标 URL 在 Filter 层和 Interceptor 层不一样,为什么?
    • 在 Filter 层,你拿到的可能是原始请求 URI。在 Interceptor 层,Spring MVC 可能已经通过 HandlerMapping 解析了路径变量或进行了前后缀匹配,导致 request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE) 等属性中保存的值与原始 URI 不同。
  • 加分回答:对于异步请求,Filter 和 Interceptor 的行为会更复杂。Filter 的 doFilter 可能在异步处理还未结束时就已经返回,而 Interceptor 有 afterConcurrentHandlingStarted 回调来处理异步启动的场景。

9. 如何在 Spring Boot 中全局禁用某个内置的 Spring Security 过滤器?

  • 回答:最简单的办法是通过安全配置排除它。在继承 WebSecurityConfigurerAdapter 的配置类中。
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // ... 其他配置
            .csrf().disable() // 这会移除CsrfFilter
            .headers().disable() // 这会移除HeaderWriterFilter等
            .sessionManagement().disable(); // 禁用session相关过滤器和策略
    }
    
    这是一种声明式的方式,它通过修改 HttpSecurity 的内部构建器状态,使得在构建最终的 SecurityFilterChain 时,不添加对应的 Filter。
  • 追问 1:如果我想禁用一个没有对应 .xx().disable() 方法的 Filter 怎么办?
    • 可以通过 http.addFilterBefore(new MyDummyFilter(), CsrfFilter.class) 的方式,添加一个什么都不做的 Filter 来“占位”,但这并不优雅。更好的方法是通过配置,使得构建该 Filter 的条件不满足,它就不会被添加到链上。
  • 追问 2:直接排除 Filter Bean 的方式可行吗?
    • 如果该 Filter 被标记为 @Bean 并被自动配置使用,可以尝试定义一个同名的 Bean 并返回一个新的无害 Filter 来覆盖,或使用 @SpringBootApplication(exclude=...)。但这种方式非常脆弱,耦合度高。通过 HttpSecurity 配置是官方推荐且稳定的做法。
  • 追问 3:禁用过滤器会带来什么安全风险?
    • 以禁用 CsrfFilter 为例,它将使应用暴露在跨站请求伪造的攻击之下。因此,在禁用一个安全过滤器前,必须深刻理解其防护的威胁,并确保有替代方案(如使用无状态的 Token 认证时通常可以关闭 CSRF)。
  • 加分回答csrf().disable() 等配置最终会通过 HttpSecurity 中对应的 AbstractConfigurer(如 CsrfConfigurer)修改 SecurityBuilder 的状态,这是一种 Builder 模式的变体应用。

10. 如果 Spring Security 过滤器链中某个过滤器抛出了异常,后续的 Filter 和 Interceptor 还会执行吗?

  • 回答:这取决于异常的类型和抛出位置。
    • 后续的安全 Filter 不会执行。因为在 VirtualFilterChain 内部,异常会中断 doFilter 的调用链。
    • 后续的 HandlerInterceptor 不会执行。因为请求未通过安全链,更无法到达 DispatcherServlet
    • 外层的普通 Servlet Filter 的“后置逻辑”依然会执行。因为安全链内部抛出的异常,会被包裹在 DelegatingFilterProxy 的调用中,然后传播给外层的 Filter Chain。只要外层 Filter 没有在它的 chain.doFilter 调用中捕获该异常,chain.doFilter() 之后的代码仍会执行。
  • 追问 1ExceptionTranslationFilter 在这里扮演什么角色?
    • 它是一个“安全网”。它被设计用来捕获其之后的安全 Filter(主要是 FilterSecurityInterceptor)抛出的 AuthenticationExceptionAccessDeniedException,并采取适当行动(如重定向到登录页),从而将异常‘消化’掉,不再向上层抛出。所以,对于它捕获的这两种异常,后续行为是“安全链正常结束,但请求被重定向”。
  • 追问 2:如果 ExceptionTranslationFilter 本身抛出了异常呢?
    • 那将是一个未被捕获的异常,它会一路抛给 Servlet 容器,最终导致一个 500 错误页,且外层的 Filter 也无法正常返回。
  • 追问 3:如何调试这种“请求半路消失”的问题?
    • FilterChainProxy.doFilterInternalExceptionTranslationFilter.doFilterDispatcherServlet.doDispatch 等关键节点设置断点,并在外层的自定义 Filter 中也设置断点,可以清晰地观察到请求在哪个环节被中断或转向。
  • 加分回答:可以使用 Spring 的 ErrorController 或注册一个 @ExceptionHandler 在全局层来记录这些由 Filter 层抛出的异常,但这依赖于请求能最终抵达 DispatcherServlet 的错误分发,对于过早中断的请求可能无效。

11. 什么是责任链模式?在 Spring 的三层拦截机制中是如何体现的?

  • 回答:责任链模式是一种行为设计模式,它将请求沿着处理者链进行传递,每个处理者可以选择处理该请求或将其传递给链中的下一个处理者。
    • Servlet Filter Chain:这是最直观的体现。FilterChain 接口的 doFilter 方法将请求依次传递给链中的下一个 Filter,直到最终资源。
    • Spring Security VirtualFilterChainFilterChainProxy 内部的 VirtualFilterChain 是另一个纯粹的责任链实现,用于驱动单个 SecurityFilterChain 中的过滤器列表。
    • HandlerExecutionChain:它将 Interceptor 和 Handler 封装成一个链。applyPreHandle 正向遍历拦截器,triggerAfterCompletion 反向遍历,也是一个复合的责任链变体。
  • 追问 1:它们与经典的责任链模式有何不同?
    • 经典的责任链模式中,每个处理者通常会有一个对下一任处理者的引用。而在 Servlet 模型中,FilterChain 接口承担了这个任务,使得 Filter 之间可以解耦。HandlerInterceptor 模型则是由 HandlerExecutionChain 驱动,完全没有在 Interceptor 之间建立引用。
  • 追问 2:Filter 链条中的顺序是如何保证的?
    • 由注册机制决定。在 web.xml 中是声明顺序,在 FilterRegistrationBean 中是 getOrder() 返回值,最终都体现为 FilterChain 实现类内部所维护的 Filter 列表的有序性。
  • 追问 3:这种设计有什么优缺点?
    • 优点:解耦、灵活,可以动态地组合和移除处理逻辑,符合开闭原则。
    • 缺点:如果链条太长,日志和调试会变得困难;某个环节的错误可能导致整个链条无法继续;必须保证有序性,否则可能引发严重业务错误。
  • 加分回答:可以提及 Netty 中的 ChannelPipelineChannelHandler,它也是经典的、面向网络事件的责任链模式实现,可以与 Servlet 的 Filter 链进行类比,说明这是一种在网络和框架层面广泛应用的解决范式。

12. 如何在 Filter 或 Interceptor 中传递数据给 Controller?有哪些线程安全的方案?

  • 回答
    • 使用 HttpServletRequest.setAttribute:这是最标准、最安全的方式。因为 HttpServletRequest 对象是请求绑定的,自然地线程安全。
    • 使用 Spring 的 RequestContextHolder:可以获取当前请求的 Request Attributes,本质上与前者相同。
    • 使用 ThreadLocal:异步 Servlet 或 WebFlux 环境下极易导致内存泄漏或数据错乱,已不推荐使用。
  • 追问 1:为什么 ThreadLocal 有风险?
    • 在传统的同步 Servlet 模型中,一个请求占用一个线程,ThreadLocal 是可用的。但在异步 Servlet 中,一个请求可能跨越多个线程,ThreadLocal 在原始线程中设置的值在新的线程中不可见。更糟的是,如果忘记清理,由于 Tomcat 会复用线程,下次分配给这个线程的请求将能读取到上一个请求遗留的数据,造成严重的数据泄漏和安全问题。
  • 追问 2InheritableThreadLocal 能解决异步问题吗?
    • InheritableThreadLocal 解决了从父线程到子线程传递值的问题,但它无法解决线程池复用场景下的数据污染问题,而且对异步上下文切换的传递支持也不完整。依然不建议使用它来传递请求上下文。
  • 追问 3:如何安全地使用 ThreadLocal
    • 如果你能100% 保证你的应用永远不会使用异步 Servlet,并且你实现了 Filter 负责在请求结束时 finally 块中清理 ThreadLocal,那么它是可用的。但在现代 Spring Boot 应用中,这种假设非常脆弱。
  • 加分回答:Spring Security 的 SecurityContextHolder 默认使用 ThreadLocal 策略,但它也提供了 MODE_INHERITABLETHREADLOCALMODE_GLOBAL 模式,并在 FilterChainProxyfinally 块中强制清理,展示了在处理上下文传递问题上的最佳实践演化路径。

13. 多个 SecurityFilterChain 同时存在时,FilterChainProxy 如何决定使用哪一个?

  • 回答FilterChainProxy 按顺序遍历其持有的 List<SecurityFilterChain>,并调用每个 SecurityFilterChainmatches(HttpServletRequest request) 方法。第一个返回 true 的链将被立即执行,之后的所有链都会被忽略。
  • 追问 1:这带来了什么配置要求?
    • 顺序至关重要。范围最小、最具体的 SecurityFilterChain 必须配置在前面。例如,/api/admin/** 的链应该在 /api/** 的链之前。通用的 /** 链必须永远排在最后,作为兜底的默认安全策略。
  • 追问 2SecurityFilterChainmatches 机制可以有多复杂?
    • 可以通过 RequestMatcher 接口实现任意复杂的匹配逻辑。HttpSecurity 默认使用 AntPathRequestMatcher,但也支持 RegexRequestMatcher 等。你甚至可以组合多个匹配器,让一个链同时匹配路径和 IP 段,只有所有匹配器都满足时才使用该链。
  • 追问 3:如果没有一个 SecurityFilterChain 匹配会怎样?
    • 此时 FilterChainProxy 将不执行任何安全逻辑,直接把请求交还给 Servlet Filter Chain 的下一个节点。这意味着请求被“静默放行”,不进行任何认证或授权。这通常是未正确配置的表现,应确保至少有一个兜底的 SecurityFilterChain
  • 加分回答:这种设计体现了CoR (Chain of Responsibility) 与 Spec 模式 (Specification Pattern) 的结合。每个 SecurityFilterChain 既是一个责任处理器,又通过其 RequestMatcher 封装了对是否处理该请求的规则(Spec),为系统提供了灵活的规则引擎。

14. 在基于微服务的架构中,如何在网关层和微服务层分别配置安全过滤器,以及如何避免重复认证?

  • 回答:这是一种分层安全模型。
    • 网关层配置:负责“粗粒度”的安全任务,如全局 CORS、限流、IP 黑白名单、客户端认证(如 OAuth2 客户端模式)、终端用户 JWT 令牌的校验和解析。此处的 Spring Security 配置重点在于验证 JWT 有效性,但不进行数据库或用户中心查询细致鉴权。
    • 微服务层配置:负责“细粒度”的业务授权。它会再次校验网关传递过来的 JWT 令牌(信任网关已签发),但主要职责是提取令牌中的 User ID 或 Roles,然后查询本地或远程权限服务,判断该用户是否有权限对特定资源(如 /orders/{id})进行操作。
  • 追问 1:如何避免下游微服务再次去认证服务器(Auth Server)核对令牌?
    • 使用 JWT:网关验证一次 JWT 签名即可信任其中的信息。下游服务可以配置 Spring Security 只验证 JWT 的签名和有效性,不再需要调用认证服务器内向验证,这是无状态认证的核心。
    • 使用 Token Relay:网关将用户身份的“引用”(如 Opaque Token)传递给下游,下游服务再拿此引用去认证服务器换取用户详细信息和权限。开销较大,适合需要极高实时安全性的场景。
  • 追问 2:如何将网关解析出的用户信息安全地传递给下游?
    • 通常通过修改或增加 HTTP 请求头。例如,网关在验证 JWT 后,将 userId、username、roles 等关键信息放入如 X-User-IDX-User-Roles 等自定义头中。下游服务的 Spring Security 需要配置一个 Filter,从这些头中提取信息并重建 Authentication 对象。
  • 追问 3:这被称为什么模式?
    • 安全断言标记语言(SAML)持有者模式或更一般化的安全上下文传播 (Security Context Propagation)。在 Spring Cloud 生态中,经常结合 Spring Session、Spring Security OAuth2 和 Feign 拦截器来半透明地实现。
  • 加分回答:可以提及 Spring Cloud Gateway 作为 API 网关,其本身也是基于 Reactive 技术栈,因此使用 WebFilter 而非 Filter。在微服务内部,保证下游服务不能从公网直接访问,只能通过网关层层转发,是保证此安全模型成立的前提。

15. (系统设计题)设计一个 API 网关的安全过滤模块,要求能够根据请求路径动态组合不同的认证方式(如 JWT、Basic Auth),并且支持运行时热插拔。请借鉴 Spring Security 的 SecurityFilterChainFilterChainProxy 的设计,给出核心架构和关键接口伪代码。

  • 回答
    // 1. 核心接口:安全策略,即一个可匹配的过滤器链单元
    interface SecurityStrategy {
        // 是否匹配当前请求
        boolean matches(HttpRequest request);
        // 获取该策略下的过滤器链
        List<SecurityFilter> getFilters();
        // 策略名称,用于动态管理和监控
        String getStrategyName();
    }
    
    // 2. 安全过滤器抽象
    interface SecurityFilter {
        // 返回是否继续执行后续过滤器和目标资源
        boolean doFilter(HttpRequest request, HttpResponse response, FilterChain chain);
    }
    
    // 3. 安全管理器,核心控制类,类似于FilterChainProxy
    public class SecurityFilterManager {
        // 使用支持并发的列表来支持动态添加和移除策略
        private List<SecurityStrategy> strategies = new CopyOnWriteArrayList<>();
    
        // 热插拔:动态注册一个策略
        public void registerStrategy(int index, SecurityStrategy strategy) {
            strategies.add(index, strategy);
        }
        // 热插拔:动态移除一个策略
        public void removeStrategy(String strategyName) {
            strategies.removeIf(s -> s.getStrategyName().equals(strategyName));
        }
    
        // 核心过滤逻辑
        public void doFilter(HttpRequest request, HttpResponse response, FilterChain originalChain) {
            // 1. 遍历所有策略,找到第一个匹配的
            SecurityStrategy matchedStrategy = null;
            for (SecurityStrategy strategy : strategies) {
                if (strategy.matches(request)) {
                    matchedStrategy = strategy;
                    break;
                }
            }
    
            // 2. 如果找不到匹配策略,执行默认拒绝策略或直接放行
            if (matchedStrategy == null) {
                originalChain.doFilter(request, response); // 或发送403
                return;
            }
    
            // 3. 为该策略构建虚拟过滤器链并执行
            List<SecurityFilter> filters = matchedStrategy.getFilters();
            VirtualSecurityFilterChain vfc = new VirtualSecurityFilterChain(filters, originalChain);
            vfc.doFilter(request, response);
        }
    
        // 内部类:虚拟过滤器链,驱动一个策略内的所有Filter执行
        private static class VirtualSecurityFilterChain implements FilterChain {
            private final List<SecurityFilter> filters;
            private final FilterChain originalChain;
            private int index = 0;
    
            // ...构造方法
    
            @Override
            public void doFilter(HttpRequest request, HttpResponse response) {
                if (index == filters.size()) {
                    // 策略内所有过滤器都通过了,交还给原始链(最终是后端业务服务)
                    originalChain.doFilter(request, response);
                } else {
                    SecurityFilter nextFilter = filters.get(index++);
                    boolean result = nextFilter.doFilter(request, response, this);
                    // filter可以返回false中断链
                    if (!result) {
                        // 中断,不继续也暂不调用原始链
                        return; 
                    }
                }
            }
        }
    }
    
    // 4. 使用示例
    // JWT认证策略
    class JwtStrategy implements SecurityStrategy {
        @Override
        public boolean matches(HttpRequest request) {
            return request.getPath().startsWith("/api/orders");
        }
        @Override
        public List<SecurityFilter> getFilters() {
            return Arrays.asList(new JwtAuthFilter(), new RateLimitFilter());
        }
        //...
    }
    // Basic Auth认证策略
    class BasicAuthStrategy implements SecurityStrategy {
        @Override
        public boolean matches(HttpRequest request) {
            return request.getPath().startsWith("/actuator");
        }
        @Override
        public List<SecurityFilter> getFilters() {
            return Arrays.asList(new BasicAuthFilter());
        }
        //...
    }
    
    // 通过动态配置中心(如Apollo, Nacos)监听配置变化,
    // 变化发生时调用 `SecurityFilterManager.registerStrategy` 或 `removeStrategy` 即可实现运行时热插拔。
    
  • 追问 1:这里的 CopyOnWriteArrayList 解决了什么问题?有什么代价?
    • 解决了在“读多写少”场景下的并发安全问题。“读”操作(遍历 chains)完全无锁,性能极高,适合请求过滤这种高频操作。“写”操作(注册/移除策略)是低频的,每次写会复制整个底层数组,写的开销大、内存占用高,但热插拔本身不是高频操作,因此代价是可接受的。
  • 追问 2:如何实现一个 Filter 在这种设计中断开链条?
    • SecurityFilter 接口所定义的,doFilter 方法返回一个 boolean 值。当 Filter 决定 “不再继续” 时(如认证失败),它不调用 chain.doFilter 并返回 falseVirtualSecurityFilterChain 看到 false 后直接返回,不再执行后续 Filter 和业务服务。
  • 追问 3:如果一个 Filter 需要处理异步事件怎么办?
    • SecurityFilter.doFilter 的返回可以设计为非阻塞的。它可以返回 true 表示“我已经启动了异步处理,链条继续”,但实际上它内部可以提交异步任务。更完善的设计需要引入 CompletableFuture 或 Reactive Stream,使整个链条异步化,复杂度会显著上升。
  • 加分回答:可以进一步阐述该架构对 AOP 的天然支持,例如通过一个 SecurityFilter 的基类或代理,为所有 Filter 的 doFilter 方法织入统一的日志、指标收集(如 Prometheus metrics)和异常处理逻辑,这正是 Spring Security 自身 ExceptionTranslationFilter 和各级基类所做的。

请求拦截机制速查表

机制注册方式作用范围是否依赖 Spring 容器主要用途典型组件/示例
Servlet Filterweb.xml@WebFilterFilterRegistrationBean由 URL 模式决定,粗粒度否 (原生) / 可 (通过FilterRegistrationBean)通用横切关注点:日志、编码、CORSOncePerRequestFilterCharacterEncodingFilter
Spring Security FilterWebSecurityConfigurerAdapterHttpSecuritySecurityFilterChainRequestMatcher 决定,可同时匹配 URL 和更多属性认证、授权、会话管理、攻击防护SecurityContextPersistenceFilter
FilterSecurityInterceptor
HandlerInterceptorWebMvcConfigurer.addInterceptorsHandlerMapping 绑定,可精确到特定 Handler 方法MVC 框架级前后处理:Handler 耗时、统一 ModelAndView 增强HandlerInterceptor 接口实现,
UserRoleInterceptor

延伸阅读

  • Spring Security 官方参考文档:Architecture
  • Servlet 3.1 规范:第 6 章 Filtering
  • Spring Framework 源码:org.springframework.web.filter 包, org.springframework.web.servlet.handler