4. 拦截器链模式:责任链模式在鉴权中的应用、Filter/Interceptor/Aspect 对比
在多租户 SaaS 鉴权拦截链路中,单一拦截点往往无法覆盖所有安全诉求。从 IP 黑白名单、Token 校验、租户上下文注入 到 细粒度权限校验,每个环节都需要独立处理且可灵活编排。本章深入剖析责任链模式在鉴权链路中的落地方式,并对比 Filter、Interceptor、Aspect 三种拦截机制的适用场景与性能差异。
4.1 责任链模式在鉴权链路中的核心价值
责任链模式(Chain of Responsibility)将多个处理器串联成链,每个处理器决定是否处理请求以及是否将请求传递给下一个处理器。在鉴权场景中,该模式带来三大优势:
- 解耦:每个鉴权步骤(如 Token 校验、租户解析、权限判定)封装为独立节点,互不依赖。
- 可编排:通过配置即可调整链路的执行顺序,无需修改核心代码。
- 短路能力:一旦某个节点校验失败,立即终止链路,避免无效计算。
💡 多租户场景提示: 责任链的每个节点都应能访问当前请求的租户上下文(TenantContext),以便根据租户级别动态决定是否跳过某些校验(如白名单租户免 Token 校验)。
4.2 Filter / Interceptor / Aspect 对比
在 Spring 生态中,实现鉴权拦截链主要有三种机制:Filter、Interceptor、Aspect。它们在执行时机、作用范围、上下文访问能力上存在显著差异。
| 对比维度 | Filter(过滤器) | Interceptor(拦截器) | Aspect(切面) |
|---|---|---|---|
| 执行层级 | Servlet 容器层 | Spring MVC 层 | AOP 代理层 |
| 作用范围 | 所有 URL 请求(含静态资源) | 仅 Controller 请求 | 任意 Bean 方法 |
| 上下文访问 | 无 Spring 上下文(需手动注入) | 可访问 Spring IoC 容器 | 完整 AOP 上下文 |
| 租户上下文注入 | 适合早期注入(如 Header 解析) | 适合 Token 校验 + 租户绑定 | 适合方法级权限校验 |
| 异常捕获粒度 | 粗粒度(仅 doFilter 异常) | 中粒度(preHandle/postHandle) | 细粒度(@Around 环绕通知) |
| 性能开销 | 低(原生 Servlet) | 中(Spring 拦截链) | 较高(动态代理 + 反射) |
| 典型鉴权场景 | IP 黑白名单、CORS、XSS 过滤 | Token 校验、租户解析、登录态 | @PreAuthorize、@HasPermission |
📌 最佳实践: 在多租户 SaaS 中,推荐使用 Filter + Interceptor + Aspect 三层组合:Filter 做全局安全过滤(IP/CORS),Interceptor 做租户上下文注入与 Token 校验,Aspect 做方法级细粒度权限控制。每层各司其职,避免单一机制过度膨胀。
4.3 三种机制的代码实现对比
以下展示同一鉴权逻辑(Token 校验 + 租户注入)在三种机制下的实现差异:
Filter 实现
@Component
public class TenantAuthFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String token = req.getHeader("X-Auth-Token");
if (token == null || !TokenValidator.validate(token)) {
((HttpServletResponse) response).sendError(401, "Invalid token");
return;
}
// 解析租户 ID 并注入 ThreadLocal
String tenantId = TokenValidator.extractTenant(token);
TenantContext.set(tenantId);
try {
chain.doFilter(request, response);
} finally {
TenantContext.clear();
}
}
}
Interceptor 实现
@Component
public class TenantAuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) {
String token = request.getHeader("X-Auth-Token");
if (token == null || !TokenValidator.validate(token)) {
response.setStatus(401);
return false;
}
String tenantId = TokenValidator.extractTenant(token);
TenantContext.set(tenantId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
TenantContext.clear();
}
}
Aspect 实现
@Aspect
@Component
public class TenantAuthAspect {
@Around("@annotation(requireAuth)")
public Object checkAuth(ProceedingJoinPoint pjp,
RequireAuth requireAuth) throws Throwable {
// 从请求上下文获取 Token(需配合 RequestContextHolder)
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest();
String token = request.getHeader("X-Auth-Token");
if (token == null || !TokenValidator.validate(token)) {
throw new AuthException("Token invalid");
}
String tenantId = TokenValidator.extractTenant(token);
TenantContext.set(tenantId);
try {
return pjp.proceed();
} finally {
TenantContext.clear();
}
}
}
4.4 责任链模式与三种机制的融合策略
在实际架构中,责任链模式并非取代 Filter/Interceptor/Aspect,而是作为 编排引擎 嵌入其中。推荐以下融合方式:
- Filter 层:实现全局安全责任链(IP 黑白名单 → CORS → 请求日志),使用
OncePerRequestFilter确保单次请求只执行一次。 - Interceptor 层:实现鉴权责任链(Token 校验 → 租户解析 → 角色判定),通过
Ordered接口控制执行顺序。 - Aspect 层:实现方法级权限责任链(@RequireRole → @RequirePermission → @DataScope),利用
@Order注解编排切面顺序。
⚠️ 避坑指南: 避免在 Filter 中执行数据库查询或远程 RPC 调用,因为 Filter 处于 Servlet 容器层,异常处理能力有限。数据库级权限校验应下沉到 Interceptor 或 Aspect 层,以便利用 Spring 的事务管理和异常处理机制。
4.5 选择建议与性能考量
根据多租户 SaaS 的典型流量特征,给出以下选择建议:
| 场景 | 推荐机制 | 原因 |
|---|---|---|
| 全局安全过滤(IP/CORS/XSS) | Filter | 在请求进入 Spring 容器前拦截,性能最优 |
| Token 校验 + 租户上下文注入 | Interceptor | 可访问 Spring 上下文,支持依赖注入 |
| 方法级细粒度权限校验 | Aspect | 支持注解驱动,灵活控制任意 Bean 方法 |
| 高并发场景(QPS > 5000) | Filter + Interceptor | 避免 AOP 动态代理带来的反射开销 |
| 复杂编排需求(动态调整链路) | 自定义责任链 + Interceptor | 通过配置中心动态增删节点 |
🔧 性能基准数据: 在同等鉴权逻辑下,Filter 的吞吐量约为 Interceptor 的 1.2 倍,Interceptor 的吞吐量约为 Aspect 的 1.5 倍。对于核心鉴权链路,建议优先使用 Filter 或 Interceptor,将 Aspect 保留给非关键路径的权限校验。