探索 Spring Filter:解锁高效编程的秘密武器

243 阅读7分钟

Spring Filter 是一个用于在 Spring 框架中实现请求过滤的机制。它可以拦截 HTTP 请求并对其进行预处理或后处理,常用于权限验证、日志记录、请求修改等场景。

应用场景

  1. 身份认证与授权

    • 在用户访问受保护资源之前,过滤器可以检查用户的身份和权限。
  2. 日志记录

    • 通过过滤器记录每个请求的信息,如IP地址、请求路径、请求参数等,以便后续分析和审计。
  3. 输入校验与数据清理

    • 对请求参数进行验证,防止SQL注入、XSS攻击等安全问题。
  4. 压缩响应数据

    • 在发送响应前,通过过滤器对响应数据进行压缩,提高传输效率。
  5. 事务管理

    • 在一次请求处理过程中,启动和结束数据库事务。
  6. 跨域资源共享(CORS)处理

    • 允许或拒绝跨域请求。

底层实现原理

Spring Filter 基于 Java Servlet API 的 javax.servlet.Filter 接口,实现了这一接口的类可以插入到 Servlet 容器的过滤器链中,在请求到达 Servlet 之前和响应返回客户端之前对请求和响应进行处理。

实现步骤

  1. 创建过滤器类

    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import java.io.IOException;
    
    public class MyFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            // 初始化代码
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            // 前置处理
            
            // 继续执行下一个过滤器或目标资源
            chain.doFilter(request, response);
            
            // 后置处理
        }
    
        @Override
        public void destroy() {
            // 销毁代码
        }
    }
    
  2. 在 Spring 配置中注册过滤器

    • 通过 Java 配置类:

      import org.springframework.boot.web.servlet.FilterRegistrationBean;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      @Configuration
      public class FilterConfig {
      
          @Bean
          public FilterRegistrationBean<MyFilter> myFilter() {
              FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>();
              registrationBean.setFilter(new MyFilter());
              registrationBean.addUrlPatterns("/api/*");
              return registrationBean;
          }
      }
      

工作流程

  1. 客户端发送 HTTP 请求到服务器。
  2. 请求被 Servlet 容器接收,并按照配置的过滤器顺序依次调用每个过滤器的 doFilter 方法。
  3. 每个过滤器在处理完成后,通过 FilterChain 对象将请求传递给下一个过滤器。
  4. 所有过滤器处理完后,请求到达目标 Servlet 或控制器。
  5. 目标 Servlet 或控制器生成响应,并依次经过所有过滤器的后置处理部分。
  6. 最终响应返回给客户端。

身份认证filter案例

步骤一:创建过滤器类

首先,我们需要创建一个实现 javax.servlet.Filter 接口的过滤器类。在该过滤器中,我们会检查请求头中是否存在有效的 Token。

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class AuthenticationFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化代码
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        String authToken = httpRequest.getHeader("Authorization");

        // 验证Token(这里假设Token为 "valid-token" 的时候通过)
        if ("valid-token".equals(authToken)) {
            // Token有效,继续处理请求
            chain.doFilter(request, response);
        } else {
            // Token无效,返回401未授权状态
            httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            httpResponse.getWriter().write("Unauthorized");
        }
    }

    @Override
    public void destroy() {
        // 销毁代码
    }
}

步骤二:注册过滤器

接下来,需要在 Spring 配置中注册这个过滤器。可以使用 Spring Boot 提供的 FilterRegistrationBean 进行注册。

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<AuthenticationFilter> authenticationFilter() {
        FilterRegistrationBean<AuthenticationFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new AuthenticationFilter());
        registrationBean.addUrlPatterns("/api/*");  // 只对 /api/* 路径下的请求应用过滤器
        return registrationBean;
    }
}

测试过滤器

为了测试过滤器,可以创建一个简单的控制器并访问受保护的端点。

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class ApiController {

    @GetMapping("/test")
    public String testEndpoint() {
        return "Hello, authenticated user!";
    }
}

运行 Spring Boot 应用程序,然后尝试使用如下命令请求 /api/test

  1. 没有 Token:

    curl -X GET http://localhost:8080/api/test
    

    返回:

    Unauthorized
    
  2. 无效 Token

    curl -X GET -H "Authorization: invalid-token" http://localhost:8080/api/test
    

    返回:

    Unauthorized
    
  3. 有效 Token

    curl -X GET -H "Authorization: valid-token" http://localhost:8080/api/test
    

    返回:

    Hello, authenticated user!
    

通过以上步骤,我们实现了一个简单的身份认证过滤器,能够根据请求头中的 Token 决定请求是否允许继续处理。这是一种常见的用于保护 API 端点的方式。

FilterChain底层源码实现

FilterChain 是 Java Servlet API 中的一个接口,它用于在多个过滤器之间传递请求和响应。每个过滤器都可以决定是否将请求和响应传递给链中的下一个过滤器或最终的目标资源(如 Servlet 或 JSP)。FilterChain 的实现类通常由 Servlet 容器提供,以保证过滤器链的正确执行。

以下是 FilterChain 接口的定义:

public interface FilterChain {
    void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException;
}

在实际应用中,最常见的 FilterChain 实现类是由具体的 Servlet 容器(如 Apache Tomcat、Jetty 等)提供的,下面我们以 Apache Tomcat 为例解释其底层实现原理。

ApplicationFilterChain 类

在 Apache Tomcat 中,ApplicationFilterChain 类是对 FilterChain 接口的具体实现,用于管理和调用一组过滤器,并最终调用目标资源。

关键字段

package org.apache.catalina.core;

public class ApplicationFilterChain implements FilterChain {

    // 已配置的过滤器数组
    private Filter[] filters = new Filter[0];

    // 当前处理的位置索引
    private int pos = 0;

    // 最终的目标资源,即 Servlet
    private Servlet servlet = null;

    // 其他相关字段和方法...
}

doFilter 方法

doFilter 方法是 FilterChain 接口的核心部分。ApplicationFilterChain 类通过这种机制依次调用链中的各个过滤器,最后调用目标 Servlet。

@Override
public void doFilter(ServletRequest request, ServletResponse response)
    throws IOException, ServletException {

    // 检查是否已经到达过滤器链的末尾
    if (pos < filters.length) {
        // 获取当前过滤器并调用它的 doFilter 方法,同时传入当前链
        Filter filter = filters[pos++];
        filter.doFilter(request, response, this);
    } else {
        // 所有过滤器都已被调用,执行最终的目标资源
        if (servlet != null) {
            servlet.service(request, response);
        }
    }
}

示例分析

假设有三个过滤器 FilterA, FilterB, FilterC 和一个目标 Servlet MyServlet 被配置成按顺序执行。当请求到达时,doFilter 方法会如下工作:

  1. 初始状态

    • pos = 0
    • filters = [FilterA, FilterB, FilterC]
    • servlet = MyServlet
  2. 第一次调用 doFilter

    • 当前 pos = 0
    • 调用 FilterA.doFilter(request, response, chain)
    • pos 增加到 1
  3. FilterA 内部调用 chain.doFilter

    • 当前 pos = 1
    • 调用 FilterB.doFilter(request, response, chain)
    • pos 增加到 2
  4. FilterB 内部调用 chain.doFilter

    • 当前 pos = 2
    • 调用 FilterC.doFilter(request, response, chain)
    • pos 增加到 3
  5. FilterC 内部调用 chain.doFilter

    • 当前 pos = 3
    • 已达到过滤器链末尾,调用最终的 MyServlet.service(request, response)

每个过滤器都会接收到一个包含下一个过滤器和最终目标的链。当一个过滤器完成其处理后,它可以调用链的 doFilter 方法来转发请求,或者直接操作响应对象并返回,从而阻止后续过滤器和目标资源的执行。

总结

FilterChain 是通过递归调用 doFilter 方法实现的。这种设计模式使得每个过滤器都有机会在请求和响应之间插入自己的逻辑,同时能够灵活地控制请求的流向。ApplicationFilterChain 类是其具体实现,通过维护一个过滤器数组和当前位置索引来逐步调用每个过滤器,直到目标资源被最终调用。

思考题1:假如存在多个自定义filter,如何指定执行顺序

在 Spring Boot 中,如果有多个自定义过滤器(filter),可以通过以下几种方式来指定它们的执行顺序:

1. 使用 @Order 注解

Spring 提供了一个 @Order 注解,可以用来控制过滤器的执行顺序。数值越小,优先级越高,越早执行。

import org.springframework.core.annotation.Order;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;

@Order(1)
public class FirstFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 过滤器逻辑
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {}
}

@Order(2)
public class SecondFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 过滤器逻辑
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {}
}

2. 使用 FilterRegistrationBean

通过 FilterRegistrationBean 可以更加灵活地注册过滤器,并设置其执行顺序。

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<FirstFilter> firstFilter() {
        FilterRegistrationBean<FirstFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new FirstFilter());
        registrationBean.addUrlPatterns("/*");
        registrationBean.setOrder(1);  // 优先级为1
        return registrationBean;
    }

    @Bean
    public FilterRegistrationBean<SecondFilter> secondFilter() {
        FilterRegistrationBean<SecondFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new SecondFilter());
        registrationBean.addUrlPatterns("/*");
        registrationBean.setOrder(2);  // 优先级为2
        return registrationBean;
    }
}

3. 使用 Spring Security 过滤器链

如果使用 Spring Security,这些过滤器会自动按照特定顺序进行配置。你可以通过继承 WebSecurityConfigurerAdapter 并重写 configure(HttpSecurity http) 方法来添加自定义过滤器,并指定它们的位置。

import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.stereotype.Component;

@Component
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(new CustomFilter1(), UsernamePasswordAuthenticationFilter.class);
        http.addFilterAfter(new CustomFilter2(), CustomFilter1.class);
    }
}

上面的例子中,我们使用 addFilterBeforeaddFilterAfter 来指定自定义过滤器相对于其他过滤器的执行顺序。