跳到正文
Joeplover
后端开发·2026-07-10·约 7 分钟阅读

Spring Boot 四层防护:拦截器、过滤器、全局异常处理、CORS 配置全解

系统性地梳理 Spring Boot 中四种请求处理机制:Filter、HandlerInterceptor、WebMvcConfigurer、@RestControllerAdvice。从执行顺序到配置方式,从代码实例到对比分析,一篇搞清楚它们的职责边界和协作关系。

Code on screen with Spring Boot logo

Spring Boot 四层防护:拦截器、过滤器、全局异常处理、CORS 配置全解

前言

写后端接口,光能 CRUD 不够。请求进来之后,要经过身份校验、参数清洗、跨域处理、异常兜底等一系列处理,才能到达 Controller。

Spring Boot 提供了四层机制来处理这些横切关注点,从请求进入 Tomcat 到返回响应的完整链路如下:

请求 → Filter → Interceptor.preHandle → Controller → Interceptor.postHandle → Interceptor.afterCompletion → 响应
                                              │
                                         异常时 → @RestControllerAdvice

这篇文章从最底层到最上层,逐一拆解。


一、Filter(过滤器)

Filter 是什么

Filter 是 Servlet 规范定义的组件,属于 Tomcat / Jetty 等 Web 容器的范畴,与 Spring 无关。

所以 Filter 的执行时机最早——请求刚到达 Tomcat,还没到 Spring 的 DispatcherServlet,它的处理就已经开始了。

Filter 能做什么

  • 设置请求/响应编码
  • XSS 过滤、SQL 注入防护
  • 请求日志(最原始级别的)
  • IP 黑名单

Filter 不能做什么

  • 拿不到 Controller 的方法名、注解——因为 Spring MVC 还没开始解析请求路径
  • 拿不到 Spring 的 Handler(比如哪个 @RequestMapping 匹配了这个请求)

代码示例

@Component
@Order(1)
public class RequestLogFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        long start = System.currentTimeMillis();

        chain.doFilter(request, response); // 放行

        long cost = System.currentTimeMillis() - start;
        System.out.println("[Filter] " + req.getMethod() + " " + req.getRequestURI() + " → " + cost + "ms");
    }
}

@Order(1) 控制多个 Filter 之间的执行顺序,数值越小越先执行。

注意:Filter 不依赖 Spring,所以即使 Spring 容器没初始化,Filter 也能跑(虽然实际中很少有这样的场景)。


二、HandlerInterceptor(拦截器)

拦截器是什么

拦截器是 Spring MVC 的组件,在请求匹配到具体的 Controller 方法之后、执行之前介入。

相比于 Filter,拦截器知道具体要调用哪个 Controller 的哪个方法。

三个方法

public interface HandlerInterceptor {
    // Controller 执行前。返回 false 就拦截,不放行
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    // Controller 执行后,视图渲染前。可以修改 ModelAndView
    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }

    // 整个请求完成后(视图渲染完)。用于清理资源
    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    }
}

典型场景

  • 登录校验(检查 token/session,不放行就重定向到登录页)
  • 权限校验(根据注解判断当前用户是否有权限)
  • 请求参数预处理(解密、解码)
  • 接口耗时统计

登录拦截器示例

@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 放过 OPTIONS 请求(跨域预检)
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            return true;
        }

        // 放过接口文档等公开路径
        String uri = request.getRequestURI();
        if (uri.startsWith("/api/auth/") || uri.startsWith("/swagger-ui/")) {
            return true;
        }

        // 校验 token
        String token = request.getHeader("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            response.setStatus(401);
            response.setContentType("application/json");
            response.getWriter().write("{"code":401,"message":"未登录"}");
            return false;
        }

        return true;
    }
}

注意:拦截器里可以直接拿到 handler 参数,它其实就是 Controller 的方法对象,通过它可以读取方法上的注解:

if (handler instanceof HandlerMethod) {
    HandlerMethod hm = (HandlerMethod) handler;
    RequiresPermission anno = hm.getMethodAnnotation(RequiresPermission.class);
    if (anno != null) {
        // 检查权限
    }
}

这一点 Filter 做不到。


三、WebMvcConfigurer(配置中心)

它是什么

WebMvcConfigurer 是 Spring MVC 的配置接口,用来注册拦截器、配置 CORS、静态资源映射、消息转换器等。

注册拦截器

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/api/**")          // 拦截 /api/ 下的所有请求
                .excludePathPatterns("/api/auth/**", // 排除登录注册接口
                                     "/api/public/**");
    }
}

配置 CORS 跨域

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*")    // file:// 本地文件也能访问
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true);
    }
}

路径匹配模式

addPathPatterns 和 excludePathPatterns 支持 Ant 风格路径:

模式匹配
/api/**/api/ 下的所有路径(包括多级)
/api/*/api/ 下的直接子路径(不含多级)
/api/auth/**/api/auth/ 下的所有路径

注意:拦截器的路径匹配是在 Spring 容器初始化时解析的,不支持运行时动态变更。


四、@RestControllerAdvice(全局异常处理)

它是什么

@RestControllerAdvice = @ControllerAdvice + @ResponseBody,是 Spring 提供的全局异常捕获机制。

当 Controller 层抛出异常时,如果没有被 try-catch 捕获,异常会一直往外抛,最终由 DispatcherServlet 交给 @ExceptionHandler 处理。

代码示例

配合之前写的 Result 类:

@RestControllerAdvice
public class GlobalExceptionHandler {

    // 处理参数校验异常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Void> handleValidation(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult().getFieldErrors().stream()
                .map(err -> err.getField() + ": " + err.getDefaultMessage())
                .collect(Collectors.joining("; "));
        return Result.badRequest(msg);
    }

    // 处理业务异常
    @ExceptionHandler(BusinessException.class)
    public Result<Void> handleBusiness(BusinessException e) {
        return Result.error(e.getCode(), e.getMessage());
    }

    // 空指针兜底
    @ExceptionHandler(NullPointerException.class)
    public Result<Void> handleNPE(NullPointerException e) {
        return Result.error(500, "服务器内部错误:空指针异常");
    }

    // 处理所有未捕获的异常(兜底)
    @ExceptionHandler(Exception.class)
    public Result<Void> handleAll(Exception e) {
        return Result.error(500, "服务器内部错误");
    }
}

自定义业务异常

public class BusinessException extends RuntimeException {
    private final int code;
    private final String message;

    public BusinessException(int code, String message) {
        super(message);
        this.code = code;
        this.message = message;
    }

    public int getCode() { return code; }
    
    @Override
    public String getMessage() { return message; }
}

用法:

if (course.getStock() <= 0) {
    throw new BusinessException(400, "课程库存不足");
}

这样 Controller 里就不用写 try-catch 了——业务只需要关心"正常流程",异常情况直接 throw,统一由 GlobalExceptionHandler 处理并包装成 Result 返回。

执行时机

@RestControllerAdvice 的 @ExceptionHandler 在 Interceptor 的 afterCompletion 之前触发。完整的异常处理链路:

Controller 抛异常 → @ExceptionHandler 捕获 → 返回响应 → afterCompletion()

五、Filter vs Interceptor vs ExceptionHandler 对比

维度FilterHandlerInterceptor@ExceptionHandler
所属层级Servlet 规范Spring MVCSpring MVC
执行时机最早,请求到 Tomcat 就触发匹配到 Controller 后触发Controller 抛出异常后
能否修改请求/响应对象✅ 能✅ 能❌ 不能(只能在异常时返回响应)
能否拿到 Controller 方法信息❌ 不能✅ HandlerMethod 里有✅ 从异常堆栈可间接知道
能否阻断请求✅ chain.doFilter 不调用✅ preHandle 返回 false✅ 通过返回错误响应
能否处理视图❌ 不关心视图✅ postHandle 能改 ModelAndView❌ 不关心视图
典型用途编码、XSS、IP 黑名单登录校验、权限、日志参数校验、业务异常兜底

六、完整链路流程图

  ┌─────────────────────────────────────────────────────────┐
  │                       Tomcat                            │
  │  ┌────────────────────────────────────────────────────┐ │
  │  │  Filter 1 (编码) → Filter 2 (XSS) → Filter 3 (日志)│ │
  │  └────────────────┬───────────────────────────────────┘ │
  │                   │链式调用                             │
  │                   ▼                                    │
  │  ┌────────────────────────────────────────────────────┐ │
  │  │        DispatcherServlet (Spring MVC 入口)         │ │
  │  └────────────────┬───────────────────────────────────┘ │
  │                   │                                     │
  │                   ▼                                     │
  │  ┌────────────────────────────────────────────────────┐ │
  │  │   HandlerMapping → 匹配到 Controller               │ │
  │  └────────────────┬───────────────────────────────────┘ │
  │                   │                                     │
  │                   ▼                                     │
  │  ┌────────────────────────────────────────────────────┐ │
  │  │   LoginInterceptor.preHandle()                     │ │
  │  │   ├── 校验通过 → 放行                              │ │
  │  │   └── 校验失败 → 返回 401,不执行 Controller       │ │
  │  └────────────────┬───────────────────────────────────┘ │
  │                   │                                     │
  │              ┌────┴────┐                                │
  │              ▼         ▼                                │
  │    Controller正常   Controller抛异常                     │
  │              │         │                                │
  │              ▼         ▼                                │
  │     Interceptor.post  @ExceptionHandler                 │
  │     Handle()          捕获异常 → 返回 Result             │
  │              │                                            │
  │              ▼                                            │
  │     Interceptor.afterCompletion() → 清理资源              │
  │                                                            │
  └────────────────────────────────────────────────────────────┘

总结

机制选型依据
Filter需要处理所有请求(包括静态资源)、不依赖 Spring
Interceptor需要访问 Controller 的方法信息、做登录/权限校验
WebMvcConfigurer配置拦截器路径、CORS、静态资源映射
@RestControllerAdvice统一异常处理,让 Controller 专注于正常流程

初学者最容易犯的错误:Filter 里做登录校验然后想拿 Controller 的注解——拿不到。登录校验应该放在 Interceptor 里,Filter 做编码、XSS 这些真正通用的东西。