Spring Boot 四层防护:拦截器、过滤器、全局异常处理、CORS 配置全解
系统性地梳理 Spring Boot 中四种请求处理机制:Filter、HandlerInterceptor、WebMvcConfigurer、@RestControllerAdvice。从执行顺序到配置方式,从代码实例到对比分析,一篇搞清楚它们的职责边界和协作关系。
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 对比
| 维度 | Filter | HandlerInterceptor | @ExceptionHandler |
|---|---|---|---|
| 所属层级 | Servlet 规范 | Spring MVC | Spring 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 这些真正通用的东西。