本文目录导读:

在Java Web开发中,规范请求拦截流程的核心在于分层治理、职责清晰、有序执行,下面我从核心原则、标准流程(Filter/Interceptor)、常见场景规范以及代码示例四个维度来梳理。
核心原则
- 单一职责:每个拦截器只负责一件事(如仅做登录校验,不做权限判断)。
- 顺序敏感:拦截器链的执行顺序至关重要(如:日志 -> 鉴权 -> 限流 -> 业务处理)。
- 无状态:拦截器内不持有用户状态,用户信息通过
ThreadLocal或请求属性传递。 - 快速失败:非核心功能(如日志)失败不应阻塞主流程,但核心功能(如鉴权)失败必须阻断。
- 性能可控:避免在拦截器中做耗时操作(如复杂计算、远程调用),必要时应引入缓存或异步。
标准流程与执行顺序
Filter(过滤器)层面(Servlet 规范)
通常用于最底层、跨切面的粗粒度处理,如字符编码、跨域、安全防护。
[客户端请求]
↓
[Filter 1: CharacterEncodingFilter] // 设置编码
↓
[Filter 2: CorsFilter] // 跨域处理
↓
[Filter 3: SecurityFilter] // XSS/CSRF 防护
↓
[DispatcherServlet] // 进入 Spring MVC
↓
[Interceptor 1: LogInterceptor] // 请求日志(前置)
↓
[Interceptor 2: AuthInterceptor] // 登录/Token 校验
↓
[Interceptor 3: RateLimitInterceptor] // 限流
↓
[Controller] // 业务处理
↓
[Interceptor 3: 后置] // 限流后置(通常无操作)
↓
[Interceptor 2: 后置] // 鉴权后置(通常无操作)
↓
[Interceptor 1: 后置/完成] // 日志记录结束时间
↓
[Filter 响应链] out
Interceptor(拦截器)层面(Spring MVC 专属)
更贴近业务逻辑,可灵活操作 ModelAndView,适合做:身份校验、权限控制、日志审计、参数预处理。
preHandle:请求前,返回true放行,返回false阻断。postHandle:Controller 执行后、视图渲染前。afterCompletion:视图渲染后、请求完全结束前(通常用于资源清理)。
常见场景的规范实现
鉴权拦截规范
- Token 校验:从 Header 或 Cookie 提取,验证签名和过期时间。
- ThreadLocal 传递用户信息:防止数据污染,请求结束务必
remove()。 - 白名单过滤:登录、注册、静态资源等接口应跳过鉴权。
日志审计规范
- 必须记录:请求路径、请求参数、响应状态码、耗时、用户ID(如有)。
- 必须脱敏:密码、身份证、手机号等敏感字段在记录前做 处理。
- 异步写入:建议通过消息队列或异步线程写日志,避免阻塞主流程。
参数校验与防篡改规范
- 前置参数校验:使用
@Valid或自定义验证器,快速返回错误。 - 签名校验:对于敏感接口,对请求体 + 时间戳做 HMAC 签名验证,防止重放攻击。
限流与防刷规范
- 基于 IP 或 Token:使用桶算法(RateLimiter)或滑动窗口。
- 优先级低于鉴权:正确区分“未登录的游客”和“已登录的恶意用户”。
实战代码示例(Spring Boot)
定义自定义拦截器(Token 校验 + 日志)
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 1. 白名单放行
String uri = request.getRequestURI();
if (uri.startsWith("/api/public/") || uri.equals("/login")) {
return true;
}
// 2. 提取 Token
String token = request.getHeader("Authorization");
if (token == null || !jwtUtil.validateToken(token)) {
response.setStatus(401);
response.getWriter().write("{\"code\":401,\"msg\":\"未授权\"}");
return false;
}
// 3. 解析用户,存入 ThreadLocal
User user = jwtUtil.parseUser(token);
UserContext.set(user);
// 4. 记录请求日志(简单示例)
log.info("用户: {} 请求: {}", user.getId(), uri);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 必须清理 ThreadLocal,防止内存泄漏
UserContext.remove();
}
}
注册拦截器并指定顺序
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private AuthInterceptor authInterceptor;
@Autowired
private RateLimitInterceptor rateLimitInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 顺序1:日志拦截器(最先执行)
registry.addInterceptor(logInterceptor)
.addPathPatterns("/**");
// 顺序2:限流拦截器
registry.addInterceptor(rateLimitInterceptor)
.addPathPatterns("/api/**")
.order(1);
// 顺序3:鉴权拦截器(最后执行,保证已限流)
registry.addInterceptor(authInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/public/**", "/login")
.order(2);
}
}
使用 Filter 做全局限流(可选)
@Component
@Order(1) // 优先级最高
public class RateLimitFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
String ip = request.getRemoteAddr();
if (!rateLimiter.tryAcquire(ip)) {
response.setStatus(429);
return;
}
chain.doFilter(request, response);
}
}
最佳实践总结
| 场景 | 使用位置 | 备注 |
|---|---|---|
| 跨域、编码 | Filter | 与业务无关,全局生效 |
| XSS/CSRF | Filter | 安全基座 |
| Token 校验 | Interceptor preHandle | 业务相关,可获取 Spring 容器 |
| 权限校验 | Interceptor preHandle | 依赖当前登录用户 |
| 请求日志 | Interceptor preHandle | 可记录请求前/后时间 |
| 参数脱敏 | Interceptor afterCompletion | 响应体已生成 |
| 全局异常 | @ControllerAdvice | 不放在拦截器中 |
| 性能不好的操作 | 异步队列 | 不要阻塞拦截流程 |
最后总结一句话:
“Filter 做底层安全与编码,Interceptor 做登录与权限,业务逻辑放 Controller,异常统一由 @ControllerAdvice 处理。”
如果你需要针对某个具体场景(如微服务下如何利用网关统一拦截、或如何设计防重放拦截),可以告诉我,我可以进一步展开。