拦截过滤器模式:Web权限校验的经典解法

说实话,拦截过滤器模式(Intercepting Filter)是我在Web开发中最常用的模式之一。它解决了一个很实际的问题:在请求到达真正的业务处理之前,我们需要做一堆预处理工作——权限校验、日志记录、参数校验、编码转换等等。你想想看,如果把这些逻辑都塞到业务代码里,那代码得多臃肿?

我在做电商平台的时候,就遇到过这种场景。用户下单前要检查是否登录、是否有权限、请求是否合法……一开始我把这些判断都写在Controller里,结果一个方法几百行,维护起来简直噩梦。后来重构时用了拦截过滤器模式,整个世界清净了。

模式核心:三个关键角色

拦截过滤器模式其实就三个核心角色,理解它们就掌握了精髓:

  • 过滤器接口(Filter):定义过滤器的统一契约
  • 具体过滤器(Concrete Filter):实现具体的过滤逻辑
  • 过滤链(Filter Chain):管理多个过滤器的执行顺序

说白了,这就是一个"责任链"的变体。但区别在于,拦截过滤器模式更强调"拦截"这个动作——每个过滤器都可以决定是否让请求继续往下走。

先看代码:Java实现

我个人习惯先定义接口,再实现具体逻辑。这样扩展起来特别方便。

// 1. 过滤器接口
public interface Filter {
    void execute(String request);
}

// 2. 具体过滤器:权限校验
public class AuthenticationFilter implements Filter {
    @Override
    public void execute(String request) {
        if (request == null || !request.contains("token")) {
            throw new SecurityException("未授权访问!");
        }
        System.out.println("权限校验通过:" + request);
    }
}

// 3. 具体过滤器:日志记录
public class LoggingFilter implements Filter {
    @Override
    public void execute(String request) {
        System.out.println("[日志] 收到请求:" + request);
    }
}

// 4. 过滤链
public class FilterChain {
    private List<Filter> filters = new ArrayList<>();
    private int index = 0;
    
    public void addFilter(Filter filter) {
        filters.add(filter);
    }
    
    public void execute(String request) {
        if (index < filters.size()) {
            Filter filter = filters.get(index++);
            filter.execute(request);
            // 继续执行下一个过滤器
            execute(request);
        }
    }
}

// 5. 客户端使用
public class Client {
    public static void main(String[] args) {
        FilterChain chain = new FilterChain();
        chain.addFilter(new AuthenticationFilter());
        chain.addFilter(new LoggingFilter());
        
        // 模拟请求
        chain.execute("GET /api/order?token=abc123");
    }
}

关键点:过滤链中的每个过滤器执行完后,需要主动调用下一个过滤器。这种递归调用的方式,保证了过滤器的执行顺序可控。

C++实现:模板与多态

C++版本我用了虚函数和多态,效果是一样的。嗯,这里要注意内存管理,别漏了析构。

#include <iostream>
#include <vector>
#include <string>

// 1. 过滤器接口
class Filter {
public:
    virtual ~Filter() {}
    virtual void execute(const std::string& request) = 0;
};

// 2. 具体过滤器:权限校验
class AuthenticationFilter : public Filter {
public:
    void execute(const std::string& request) override {
        if (request.find("token") == std::string::npos) {
            throw std::runtime_error("未授权访问!");
        }
        std::cout << "权限校验通过:" << request << std::endl;
    }
};

// 3. 具体过滤器:日志记录
class LoggingFilter : public Filter {
public:
    void execute(const std::string& request) override {
        std::cout << "[日志] 收到请求:" << request << std::endl;
    }
};

// 4. 过滤链
class FilterChain {
private:
    std::vector<Filter*> filters;
    size_t index = 0;
    
public:
    void addFilter(Filter* filter) {
        filters.push_back(filter);
    }
    
    void execute(const std::string& request) {
        if (index < filters.size()) {
            Filter* filter = filters[index++];
            filter->execute(request);
            execute(request);  // 递归调用下一个
        }
    }
    
    ~FilterChain() {
        for (auto f : filters) delete f;
    }
};

// 5. 使用示例
int main() {
    FilterChain chain;
    chain.addFilter(new AuthenticationFilter());
    chain.addFilter(new LoggingFilter());
    
    chain.execute("GET /api/order?token=abc123");
    return 0;
}

避坑指南:我曾经在C++版本中忘记处理过滤器的生命周期,导致内存泄漏。建议使用智能指针(如std::shared_ptr)来管理过滤器对象,或者像上面这样在析构函数中统一释放。

Web应用中的权限校验实战

在实际的Web框架中(比如Java的Servlet Filter、Spring的Interceptor),拦截过滤器模式被广泛使用。我拿一个典型的权限校验场景来说:

  1. 请求到达:用户发起HTTP请求
  2. 过滤器链开始执行
    • 第一个过滤器:检查请求是否包含有效的Session或Token
    • 第二个过滤器:检查用户角色是否有权限访问该资源
    • 第三个过滤器:记录访问日志
  3. 通过所有过滤器:请求到达真正的业务处理器
  4. 任一过滤器拒绝:直接返回错误响应,不再继续执行

你想想看,这种设计最大的好处是什么?解耦。权限校验的逻辑和业务逻辑完全分离,改权限规则时不用动业务代码,改业务逻辑时也不用担心影响权限校验。

核心流程图

下面这张图展示了拦截过滤器模式的完整流程。我特意把"请求被拦截"和"请求通过"两种情况都画出来了:

拦截过滤器模式流程图 客户端请求 过滤器1:Token校验 校验通过? 过滤器2:角色校验 有权限? 业务处理器 返回响应 返回401错误 返回403错误

实际项目中的最佳实践

我在多个项目中总结了几条经验,分享给你:

场景 推荐做法 不推荐做法
过滤器顺序 先做轻量级校验(如Token),再做重量级校验(如数据库查询) 把耗时操作放在前面,导致无效请求也消耗资源
过滤器复用 将通用逻辑(如日志、编码转换)抽取为独立过滤器 每个业务模块都写重复的校验代码
异常处理 在过滤链末尾添加一个全局异常过滤器 每个过滤器各自处理异常,导致错误响应格式不统一
性能考虑 对频繁调用的过滤器做缓存(如角色权限缓存) 每次请求都查数据库,不考虑缓存

特别注意:过滤器的执行顺序非常重要!我曾经在一个项目中,把日志过滤器放在了权限过滤器前面,结果每次未授权的请求都先记录日志,导致日志里全是"未授权"的错误记录,排查问题时根本找不到有效信息。正确的做法是:权限校验在前,日志记录在后

模式优缺点总结

用这个模式这么多年,我觉得它的优缺点都很明显:

优点

  • 关注点分离:每个过滤器只做一件事,符合单一职责原则
  • 可插拔:新增或移除过滤器不影响现有代码
  • 可复用:同一个过滤器可以应用到不同的请求路径上

缺点

  • 调试困难:过滤器链较长时,定位问题需要逐个排查
  • 性能开销:每个请求都要经过多个过滤器,会有一定的性能损耗
  • 顺序依赖:过滤器之间的顺序如果搞错,可能导致逻辑错误

嗯,总的来说,拦截过滤器模式是Web开发中不可或缺的工具。我个人建议,只要你的应用需要做前置处理(权限、日志、校验等),优先考虑这个模式。它可能不是最炫酷的设计模式,但绝对是最实用的之一。


公众号:蓝海资料掘金营,微信deep3321