文章目录
一、核心对比:过滤器和拦截器的本质差异
维度 | 过滤器(Filter) | 拦截器(Interceptor) |
---|---|---|
规范基础 | 基于Servlet规范(Java EE标准) | 基于Spring MVC框架实现 |
作用范围 | 对所有请求生效(包括静态资源),处理原始HTTP数据(如头信息、参数) | 仅作用于Controller层请求,可访问Spring上下文和业务对象(如ModelAndView) |
执行时机 | 在Servlet处理前和响应返回前执行,属于Web容器责任链 | 在Controller方法执行前后触发(preHandle 、postHandle ) |
配置方式 | 通过web.xml 或@WebFilter 注解 | 通过实现WebMvcConfigurer 接口注册 |
依赖关系 | 不依赖Spring框架,通用性强 | 依赖Spring框架,可无缝集成Bean(如Service层对象) |
典型应用 | - 请求/响应编码转换 - XSS/SQL注入防御 - 全局日志记录 | - 细粒度权限校验 - 接口耗时监控 - 业务参数预处理 |
性能影响 | 更底层,性能损耗小 | 需要经过Spring上下文 |
异常处理 | 无法捕获Controller异常 | 可通过@ControllerAdvice处理 |
1.1 过滤器(Filter)执行阶段
1.2 拦截器(Interceptor)执行阶段
二、选型决策树:何时用过滤器?何时用拦截器?
2.1. 过滤器使用场景
- 请求预处理:参数清洗、请求包装
- 响应后处理:压缩响应内容、添加统一Header
- 安全防护:XSS过滤(案例2)、CSRF校验
- 流量控制:限流(案例3)、黑白名单
2.2. 拦截器适用场景
- 认证授权:JWT解析、权限校验(案例1)
- 业务校验:参数合法性检查
- 日志记录:接口调用追踪
- 性能监控:方法执行耗时统计
2.3. 混合使用注意事项
- 执行顺序控制:FilterChain顺序 > Interceptor顺序
- 避免重复处理:网关层与业务层的鉴权分工
- 上下文传递:使用ThreadLocal跨层级传递数据
- 性能优化:缓存频繁访问的数据(如案例3的鉴权URL列表)
三、注意事项:开发中的关键细节
- 执行顺序控制:
- 过滤器通过
@Order
注解或web.xml
配置顺序,拦截器通过注册顺序决定。 - 案例3中Gateway过滤器的
getOrder()
方法设置优先级为0,需根据业务调整。
- 过滤器通过
- 性能优化:
- 高频操作(如案例3的IP限流)需结合Redis缓存,避免数据库压力。
- 拦截器中避免阻塞IO操作(如远程调用),改用异步处理。
- 异常处理:
- 过滤器中异常需通过
response.sendError()
直接返回,拦截器可用@ControllerAdvice
统一处理。 - 案例1中通过抛出
BaseException
触发全局异常处理器。
- 过滤器中异常需通过
- 动态配置:
- 拦截器的排除路径(如案例1的
securityProperties.getExcludes()
)应支持动态加载。
- 拦截器的排除路径(如案例1的
四、实战案例解析
案例1:登录鉴权拦截器实现
白名单配置
@Data
@ConfigurationProperties("security")
public class SecurityProperties {
private List<String> excludes;
}
security:
excludes:
# 静态资源
- /*.html
- /**/*.html
- /**/*.css
- /**/*.js
# swagger 文档配置
- /favicon.ico
- /*/api-docs
- /*/api-docs/**
- /swagger-resources
- /swagger-resources/**
- /zyw/error
- /doc.html
# druid 监控配置
- /druid/**
# 登录
- /sys/login
- /sys/getVerificationCode
- /sys/registration
# 测试
- /test/**
# 本地文件
- /static/**
配置Sa-Token 路由拦截器
@Configuration
@EnableConfigurationProperties(SecurityProperties.class)
@RequiredArgsConstructor
public class SaTokenConfigure implements WebMvcConfigurer {
private final SecurityProperties securityProperties;
private final SysUserService userService;
/**
* 注册sa-token的拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验。
List<String> excludes = securityProperties.getExcludes();
registry.addInterceptor(new SaInterceptor(handle -> {
StpUtil.checkLogin();
// 验证当前登录用户的账户状态
validateUserStatus();
}))
.addPathPatterns("/**")
.excludePathPatterns(CollectionUtil.isNotEmpty(excludes) ? excludes : new ArrayList<>());
}
/**
* 验证当前登录用户的账户状态
*/
private void validateUserStatus() {
Long userId = StpUtil.getLoginIdAsLong();
SysUser user = userService.getById(userId);
if (Objects.isNull(user)) {
throw new BaseException(ResultCode.UNAUTHORIZED);
}
if (SysUser.FORBIDDEN.equals(user.getAccountStatus())) {
throw new BaseException(ResultCode.UNAUTHORIZED);
}
}
}
- 设计要点:
- 使用
excludePathPatterns
灵活配置白名单 - 通过Service层获取完整用户信息
- 采用异常机制中断非法请求
- 使用
案例2:XSS防御过滤器
(XSS防御详细思路及实现方式可查看博主的另一篇博客:存储型XSS攻防全解析:构建坚不可摧的Web应用防线)
-
过滤器最佳实践(案例2):
@Component @WebFilter(filterName = "XSSFilter", /** * 通配符(*)表示对所有的web资源进行拦截 */ urlPatterns = "/*" ) public class XSSFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { PrintWriter writer = response.getWriter(); //过滤请求 XssRequestWrappers xssRequestWrappers = new XssRequestWrappers((HttpServletRequest) request); //过滤返回 ResponseWrapper wrapperResponse = new ResponseWrapper((HttpServletResponse) response);//转换成代理类 chain.doFilter(xssRequestWrappers, wrapperResponse); byte[] content = wrapperResponse.getContent();//获取返回值 //判断是否有值 if (content.length > 0) { String str = new String(content, "UTF-8"); String ciphertext = null; try { //......根据需要处理返回值 ciphertext = XSSUtils.striptXSS(str); } catch (Exception e) { e.printStackTrace(); } //把返回值输出到客户端 writer.append(ciphertext); writer.close(); } } }
- 封装要点:
- 继承
HttpServletRequestWrapper
- 重写
getParameterValues()
等方法 - 使用Jsoup进行HTML标签清理
- 继承
案例3:网关全局过滤器
@Component public class MyLogGateWayFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // IP频率控制逻辑 String ip = IpUtils.getIP(request); redisService.incr(RedisEnum.IpFrequency.getKey(ip), 10L); // 动态鉴权路径检查 if(checkUrl(request.getPath())){ String token = request.getHeaders().getFirst("Authorization"); // JWT有效性校验 Claims claims = JwtParseUtil.parseJWT(token); } return chain.filter(exchange); } }
- 架构优势:
- 前置流量控制点
- 动态路由鉴权配置
- 统一令牌校验
- 封装要点:
五、常见陷阱规避
- 循环依赖问题:
- 避免在Filter中直接注入Service(可通过Lazy初始化解决)
- 若需在过滤器中注入Spring Bean(如案例3的
RedisService
),需使用DelegatingFilterProxy
代理。
-
路径匹配失效:
动态URL规则(如案例3的
checkUrl
方法)需使用正则表达式或Ant风格路径匹配,避免遗漏。// 错误示例:未考虑context-path .excludePathPatterns("/api/public/**") // 正确做法:动态获取上下文路径 String contextPath = exchange.getRequest().getPath().contextPath().value();
-
异步请求处理:
- 在afterCompletion中清理ThreadLocal变量
- 使用AsyncContext处理长时间任务
-
性能瓶颈:
- 对Redis高频访问操作添加本地缓存(Caffeine)
- 采用布隆过滤器优化权限校验
-
AOP与拦截器的混淆:
- AOP无法拦截私有方法或静态方法,需明确区分拦截器和切面的适用场景。
六、总结与案例映射
通过上述框架,用户提供的三个案例可精准映射到不同模块:
- 案例1(Sa-Token拦截器):典型拦截器应用,处理登录状态和业务权限。
- 案例2(XSS过滤器):过滤器实现全局请求参数净化。
- 案例3(Gateway过滤器):综合过滤器的微服务入口鉴权与限流。
通过合理选型与规避陷阱,可构建高效、安全的企业级Java应用。过滤器和拦截器如同系统的"门神"与"管家",前者把守入口进行粗粒度控制,后者专注业务进行精细化管理。理解二者的本质区别,根据实际场景合理选用,是构建健壮企业级应用的关键。随着云原生技术的发展,新一代的Proxyless Service Mesh架构正在模糊传统分层边界,但对请求处理链路的掌控能力始终是开发者必备的核心技能。
七、未来演进方向
-
云原生适配:
- 使用Envoy Filter实现Service Mesh层过滤
- 开发Kubernetes Admission Webhook
-
智能防护:
- 集成ML模型识别异常流量
- 动态调整限流阈值
-
可观测性增强:
- 接入Micrometer指标
- 生成分布式链路追踪标签