4. 拦截器链模式:责任链模式在鉴权中的应用、Filter/Interceptor/Aspect 对比

在多租户 SaaS 鉴权拦截链路中,单一拦截点往往无法覆盖所有安全诉求。从 IP 黑白名单、Token 校验、租户上下文注入 到 细粒度权限校验,每个环节都需要独立处理且可灵活编排。本章深入剖析责任链模式在鉴权链路中的落地方式,并对比 Filter、Interceptor、Aspect 三种拦截机制的适用场景与性能差异。

4.1 责任链模式在鉴权链路中的核心价值

责任链模式(Chain of Responsibility)将多个处理器串联成链,每个处理器决定是否处理请求以及是否将请求传递给下一个处理器。在鉴权场景中,该模式带来三大优势:

  • 解耦:每个鉴权步骤(如 Token 校验、租户解析、权限判定)封装为独立节点,互不依赖。
  • 可编排:通过配置即可调整链路的执行顺序,无需修改核心代码。
  • 短路能力:一旦某个节点校验失败,立即终止链路,避免无效计算。
💡 多租户场景提示: 责任链的每个节点都应能访问当前请求的租户上下文(TenantContext),以便根据租户级别动态决定是否跳过某些校验(如白名单租户免 Token 校验)。
责任链模式鉴权执行流程 责任链模式鉴权执行流程 HTTP 请求 ① IP 黑白名单校验 ② Token 校验 ③ 租户上下文注入 ④ 细粒度权限校验 短路机制 任一节点返回失败, 立即终止链路, 返回 401/403 响应。

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 的事务管理和异常处理机制。
Filter / Interceptor / Aspect 三层鉴权架构 Filter / Interceptor / Aspect 三层鉴权架构 Filter 层(Servlet 容器) IP 黑白名单 → CORS 过滤 → 请求日志 → 全局异常捕获 责任链节点:OncePerRequestFilter + @Order Interceptor 层(Spring MVC) Token 校验 → 租户上下文注入 → 角色判定 → 请求预处理 责任链节点:HandlerInterceptor + 注册顺序 Aspect 层(AOP 代理) @RequireRole → @RequirePermission → @DataScope → 方法环绕 责任链节点:@Around + @Order 切面排序 异常捕获范围 Filter:全局 Servlet 异常 Interceptor:Controller 层异常 Aspect:方法级精确异常

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 保留给非关键路径的权限校验。