《深入理解Spring》过滤器(Filter)——Web请求的第一道防线

简介: Servlet过滤器是Java Web核心组件,可在请求进入容器时进行预处理与响应后处理,适用于日志、认证、安全、跨域等全局性功能,具有比Spring拦截器更早的执行时机和更广的覆盖范围。

1. 引言:过滤器在Web架构中的核心地位

在现代Web应用开发中,请求处理往往需要经过多个层次的检查和加工。Spring拦截器虽然功能强大,但它仅仅是Spring MVC框架内部的处理机制。而在更广泛的Web层面,我们需要一个更加基础、更加通用的机制来处理所有HTTP请求,这就是Servlet过滤器(Filter)

过滤器是Java Servlet规范定义的标准组件,它位于Web容器层面,能够拦截所有进入应用的HTTP请求和响应。与Spring拦截器相比,过滤器的控制范围更广,它可以处理静态资源、JSP页面以及其他非Spring管理的请求。

比喻:如果将Web应用比作一座城堡,那么过滤器就是城堡外围的安检系统和防御工事。所有进出城堡的人员(请求和响应)都必须经过这些关卡的检查和处理,而Spring拦截器更像是城堡内部特定区域的警卫。

2. 过滤器 vs 拦截器:职责边界与协作关系

为了更好地理解过滤器的定位,我们需要明确它与拦截器的区别和协作方式:

特性

过滤器 (Filter)

拦截器 (Interceptor)

规范标准

Java Servlet规范

Spring框架特有

作用范围

所有HTTP请求(包括静态资源)

仅Spring MVC控制的请求

依赖关系

不依赖Spring,属于Web容器

依赖Spring容器和上下文

执行时机

在Servlet之前执行

在DispatcherServlet之后执行

访问能力

只能访问Servlet API

可以访问Spring的Bean和上下文

下面是过滤器与拦截器在请求处理流程中的协同工作示意图:

3. 过滤器核心接口与工作机制

3.1 Filter接口详解

Servlet过滤器的核心是javax.servlet.Filter接口,它定义了三个方法:


public interface Filter {
    // 初始化方法 - 容器启动时调用一次
    default void init(FilterConfig filterConfig) throws ServletException {}
    
    // 过滤方法 - 每次请求都会调用
    void doFilter(ServletRequest request, ServletResponse response, 
                 FilterChain chain) throws IOException, ServletException;
    
    // 销毁方法 - 容器关闭时调用一次
    default void destroy() {}
}

3.2 过滤器链机制

过滤器的核心机制是过滤器链(FilterChain),多个过滤器按照配置顺序形成处理链:


public void doFilter(ServletRequest request, ServletResponse response, 
                     FilterChain chain) throws IOException, ServletException {
    // 1. 预处理逻辑(在调用chain.doFilter之前)
    System.out.println("过滤器预处理: " + ((HttpServletRequest) request).getRequestURI());
    
    // 2. 调用过滤器链中的下一个过滤器
    // 如果没有更多过滤器,最终会调用目标Servlet
    chain.doFilter(request, response);
    
    // 3. 后处理逻辑(在调用chain.doFilter之后)
    System.out.println("过滤器后处理: " + ((HttpServletResponse) response).getStatus());
}

4. 实战演练:自定义过滤器开发

4.1 创建自定义过滤器

让我们通过几个实际案例来演示如何创建和使用过滤器。

示例1:请求日志过滤器


@Component
@Order(1) // 指定过滤器执行顺序
public class LoggingFilter implements Filter {
    
    private static final Logger logger = LoggerFactory.getLogger(LoggingFilter.class);
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        long startTime = System.currentTimeMillis();
        
        // 包装Response以捕获响应内容(可选)
        ContentCachingResponseWrapper responseWrapper = 
            new ContentCachingResponseWrapper(httpResponse);
        
        try {
            // 记录请求开始日志
            logger.info("请求开始: {} {}, 客户端IP: {}", 
                       httpRequest.getMethod(), 
                       httpRequest.getRequestURI(),
                       httpRequest.getRemoteAddr());
            
            // 继续过滤器链
            chain.doFilter(request, responseWrapper);
            
        } finally {
            // 记录请求完成日志
            long duration = System.currentTimeMillis() - startTime;
            logger.info("请求完成: {} {}, 状态码: {}, 耗时: {}ms", 
                       httpRequest.getMethod(), 
                       httpRequest.getRequestURI(),
                       httpResponse.getStatus(),
                       duration);
            
            // 确保响应内容被写入客户端
            responseWrapper.copyBodyToResponse();
        }
    }
}

示例2:身份认证过滤器


@Component
@Order(2)
public class AuthenticationFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        // 检查是否需要认证
        if (requiresAuthentication(httpRequest)) {
            // 检查认证令牌
            String token = httpRequest.getHeader("Authorization");
            
            if (token == null || !isValidToken(token)) {
                // 认证失败,返回401错误
                httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
                httpResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
                
                String errorResponse = "{\"error\": \"未授权的访问\", \"code\": 401}";
                response.getWriter().write(errorResponse);
                return; // 中断请求处理
            }
            
            // 认证成功,可以设置用户信息到请求属性中
            String userId = extractUserIdFromToken(token);
            request.setAttribute("userId", userId);
        }
        
        // 继续过滤器链
        chain.doFilter(request, response);
    }
    
    private boolean requiresAuthentication(HttpServletRequest request) {
        String path = request.getRequestURI();
        // 排除登录接口和静态资源
        return !path.contains("/login") && !path.contains("/public/");
    }
    
    private boolean isValidToken(String token) {
        // 实际的令牌验证逻辑
        return token != null && token.startsWith("Bearer ");
    }
    
    private String extractUserIdFromToken(String token) {
        // 从令牌中提取用户ID(实际项目应使用JWT等标准令牌)
        return "user123";
    }
}

示例3:CORS跨域过滤器


@Component
@Order(3)
public class CorsFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        // 设置CORS头部
        httpResponse.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
        httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpResponse.setHeader("Access-Control-Max-Age", "3600");
        
        // 处理预检请求
        if ("OPTIONS".equalsIgnoreCase(httpRequest.getMethod())) {
            httpResponse.setStatus(HttpStatus.OK.value());
            return;
        }
        
        chain.doFilter(request, response);
    }
}

4.2 注册过滤器的多种方式

在Spring Boot中,有多种方式注册过滤器:

方式1:使用@Component + @Order(推荐)


@Component
@Order(1)
public class MyFilter implements Filter {
    // 自动注册,顺序通过@Order指定
}

方式2:使用FilterRegistrationBean(更灵活控制)


@Configuration
public class FilterConfig {
    
    @Bean
    public FilterRegistrationBean<LoggingFilter> loggingFilter() {
        FilterRegistrationBean<LoggingFilter> registrationBean = 
            new FilterRegistrationBean<>();
        
        registrationBean.setFilter(new LoggingFilter());
        registrationBean.addUrlPatterns("/*");
        registrationBean.setOrder(1);
        registrationBean.setName("loggingFilter");
        
        return registrationBean;
    }
    
    @Bean
    public FilterRegistrationBean<AuthenticationFilter> authFilter() {
        FilterRegistrationBean<AuthenticationFilter> registrationBean = 
            new FilterRegistrationBean<>();
        
        registrationBean.setFilter(new AuthenticationFilter());
        registrationBean.addUrlPatterns("/api/*");
        registrationBean.setOrder(2);
        registrationBean.setName("authenticationFilter");
        
        return registrationBean;
    }
}

4.3 测试过滤器效果

创建测试控制器验证过滤器工作:


@RestController
@RequestMapping("/api")
public class TestController {
    
    @GetMapping("/public/data")
    public ResponseEntity<String> publicData() {
        return ResponseEntity.ok("这是公开数据,不需要认证");
    }
    
    @GetMapping("/private/data")
    public ResponseEntity<String> privateData(@RequestAttribute(value = "userId", required = false) String userId) {
        if (userId == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("需要认证");
        }
        return ResponseEntity.ok("这是私有数据,用户ID: " + userId);
    }
    
    @PostMapping("/login")
    public ResponseEntity<Map<String, String>> login(@RequestParam String username, 
                                                    @RequestParam String password) {
        // 模拟登录逻辑
        Map<String, String> response = new HashMap<>();
        response.put("token", "Bearer mock_token_" + username);
        response.put("username", username);
        return ResponseEntity.ok(response);
    }
}

使用curl命令测试过滤器:


# 测试公开接口(应正常返回)
curl http://localhost:8080/api/public/data
# 测试私有接口(应返回401错误)
curl http://localhost:8080/api/private/data
# 登录获取令牌
curl -X POST -d "username=test&password=123" http://localhost:8080/api/login
# 使用令牌访问私有接口
curl -H "Authorization: Bearer mock_token_test" http://localhost:8080/api/private/data

5. 高级应用与最佳实践

5.1 请求/响应包装器

过滤器可以修改请求和响应,但需要包装原始对象:


public class CustomWrapperFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        // 包装请求以修改参数
        HttpServletRequest wrappedRequest = new HttpServletRequestWrapper((HttpServletRequest) request) {
            @Override
            public String getParameter(String name) {
                String value = super.getParameter(name);
                return value != null ? value.toUpperCase() : null;
            }
        };
        
        // 包装响应以捕获内容
        ContentCachingResponseWrapper wrappedResponse = 
            new ContentCachingResponseWrapper((HttpServletResponse) response);
        
        chain.doFilter(wrappedRequest, wrappedResponse);
        
        // 处理响应内容
        byte[] content = wrappedResponse.getContentAsByteArray();
        if (content.length > 0) {
            String contentString = new String(content, wrappedResponse.getCharacterEncoding());
            // 可以修改响应内容
            // ...
            
            // 更新响应
            wrappedResponse.resetBuffer();
            wrappedResponse.getWriter().write(contentString);
        }
        
        wrappedResponse.copyBodyToResponse();
    }
}

5.2 性能监控过滤器


@Component
public class MetricsFilter implements Filter {
    
    private final MeterRegistry meterRegistry;
    
    public MetricsFilter(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        long startTime = System.currentTimeMillis();
        
        try {
            chain.doFilter(request, response);
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            int status = ((HttpServletResponse) response).getStatus();
            
            // 记录指标
            meterRegistry.timer("http.requests")
                .tags("uri", httpRequest.getRequestURI(), 
                      "method", httpRequest.getMethod(),
                      "status", String.valueOf(status))
                .record(duration, TimeUnit.MILLISECONDS);
        }
    }
}

5.3 安全防护过滤器


@Component
public class SecurityFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        // 1. XSS防护:清理请求参数
        Map<String, String[]> parameterMap = httpRequest.getParameterMap();
        for (String key : parameterMap.keySet()) {
            String[] values = parameterMap.get(key);
            for (int i = 0; i < values.length; i++) {
                values[i] = cleanXSS(values[i]);
            }
        }
        
        // 2. CSRF防护:检查CSRF令牌
        if ("POST".equalsIgnoreCase(httpRequest.getMethod()) || 
            "PUT".equalsIgnoreCase(httpRequest.getMethod()) || 
            "DELETE".equalsIgnoreCase(httpRequest.getMethod())) {
            
            String csrfToken = httpRequest.getHeader("X-CSRF-TOKEN");
            String sessionToken = (String) httpRequest.getSession().getAttribute("csrfToken");
            
            if (csrfToken == null || !csrfToken.equals(sessionToken)) {
                httpResponse.setStatus(HttpStatus.FORBIDDEN.value());
                response.getWriter().write("CSRF token validation failed");
                return;
            }
        }
        
        // 3. 设置安全相关的HTTP头
        httpResponse.setHeader("X-Content-Type-Options", "nosniff");
        httpResponse.setHeader("X-Frame-Options", "DENY");
        httpResponse.setHeader("X-XSS-Protection", "1; mode=block");
        httpResponse.setHeader("Strict-Transport-Security", "max-age=31536000");
        
        chain.doFilter(request, response);
    }
    
    private String cleanXSS(String value) {
        if (value == null) return null;
        return value.replaceAll("<", "<")
                    .replaceAll(">", ">")
                    .replaceAll("\"", """)
                    .replaceAll("'", "'")
                    .replaceAll("/", "/");
    }
}

6. 常见问题与解决方案

6.1 过滤器执行顺序问题

问题:多个过滤器的执行顺序不符合预期。

解决方案

  1. 使用@Order注解指定顺序(数字越小优先级越高)
  2. 使用FilterRegistrationBean明确设置order值
  3. 避免循环依赖,确保过滤器之间没有复杂的依赖关系

6.2 性能优化建议

  1. 避免频繁的对象创建:在init方法中初始化资源,而不是在doFilter
  2. 使用异步处理:对于耗时操作,考虑使用异步过滤器
  3. 合理设置过滤路径:避免对静态资源等不需要处理的请求进行过滤
  4. 使用缓存:对重复的计算结果进行缓存

6.3 异常处理策略


public class ExceptionHandlingFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        try {
            chain.doFilter(request, response);
        } catch (Exception e) {
            // 统一异常处理
            handleException(e, (HttpServletRequest) request, (HttpServletResponse) response);
        }
    }
    
    private void handleException(Exception e, HttpServletRequest request, 
                               HttpServletResponse response) throws IOException {
        
        logger.error("请求处理异常: {} {}", request.getMethod(), request.getRequestURI(), e);
        
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        
        Map<String, Object> errorResponse = new HashMap<>();
        errorResponse.put("timestamp", System.currentTimeMillis());
        errorResponse.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
        errorResponse.put("error", "Internal Server Error");
        errorResponse.put("message", "系统内部错误,请稍后重试");
        errorResponse.put("path", request.getRequestURI());
        
        // 开发环境显示详细错误信息
        if (isDevelopmentEnvironment()) {
            errorResponse.put("exception", e.getClass().getName());
            errorResponse.put("detail", e.getMessage());
        }
        
        response.getWriter().write(new ObjectMapper().writeValueAsString(errorResponse));
    }
}

7. 总结

Servlet过滤器是Java Web开发中不可或缺的基础组件,它提供了以下核心价值:

  1. 全局性处理能力:能够拦截所有HTTP请求,包括静态资源和非Spring管理的请求
  2. 预处理和后处理:可以在请求到达Servlet之前和响应返回客户端之后执行逻辑
  3. 请求/响应修改:能够修改请求参数和响应内容
  4. 安全防护:提供统一的安全检查、认证授权机制

通过合理使用过滤器,你可以:

  • 实现统一的日志记录和性能监控
  • 构建强大的安全防护体系
  • 处理跨域请求和编码问题
  • 实现请求/响应的转换和增强

最佳实践提示

保持过滤器职责单一,每个过滤器只关注一个特定功能

注意过滤器执行顺序,避免意外的副作用

考虑性能影响,避免在过滤器中执行耗时操作

妥善处理异常,确保请求链不会意外中断

过滤器与拦截器各有其适用场景,在实际项目中往往需要结合使用,共同构建完整的请求处理管道。

相关文章
|
3月前
|
负载均衡 监控 Java
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
本文详细介绍了 Spring Cloud Gateway 的核心功能与实践配置。首先讲解了网关模块的创建流程,包括依赖引入(gateway、nacos 服务发现、负载均衡)、端口与服务发现配置,以及路由规则的设置(需注意路径前缀重复与优先级 order)。接着深入解析路由断言,涵盖 After、Before、Path 等 12 种内置断言的参数、作用及配置示例,并说明了自定义断言的实现方法。随后重点阐述过滤器机制,区分路由过滤器(如 AddRequestHeader、RewritePath、RequestRateLimiter 等)与全局过滤器的作用范围与配置方式,提
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
|
2月前
|
缓存 监控 Java
《深入理解Spring》拦截器(Interceptor)——请求处理的艺术
Spring拦截器是Web开发中实现横切关注点的核心组件,基于AOP思想,可在请求处理前后执行日志记录、身份验证、权限控制等通用逻辑。相比Servlet过滤器,拦截器更贴近Spring容器,能访问Bean和上下文,适用于Controller级精细控制。通过实现`HandlerInterceptor`接口的`preHandle`、`postHandle`和`afterCompletion`方法,可灵活控制请求流程。结合配置类注册并设置路径匹配与执行顺序,实现高效复用与维护。常用于认证鉴权、性能监控、统一异常处理等场景,提升应用安全性与可维护性。
|
3月前
|
存储 安全 Java
如何在 Spring Web 应用程序中使用 @SessionScope 和 @RequestScope
Spring框架中的`@SessionScope`和`@RequestScope`注解用于管理Web应用中的状态。`@SessionScope`绑定HTTP会话生命周期,适用于用户特定数据,如购物车;`@RequestScope`限定于单个请求,适合无状态、线程安全的操作,如日志记录。合理选择作用域能提升应用性能与可维护性。
153 1
|
4月前
|
存储 NoSQL Java
探索Spring Boot的函数式Web应用开发
通过这种方式,开发者能以声明式和函数式的编程习惯,构建高效、易测试、并发友好的Web应用,同时也能以较小的学习曲线迅速上手,因为这些概念与Spring Framework其他部分保持一致性。在设计和编码过程中,保持代码的简洁性和高内聚性,有助于维持项目的可管理性,也便于其他开发者阅读和理解。
144 0
|
5月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
339 0
|
5月前
|
JSON 前端开发 Java
Spring MVC 核心组件与请求处理机制详解
本文解析了 Spring MVC 的核心组件及请求流程,核心组件包括 DispatcherServlet(中央调度)、HandlerMapping(URL 匹配处理器)、HandlerAdapter(执行处理器)、Handler(业务方法)、ViewResolver(视图解析),其中仅 Handler 需开发者实现。 详细描述了请求执行的 7 步流程:请求到达 DispatcherServlet 后,经映射器、适配器找到并执行处理器,再通过视图解析器渲染视图(前后端分离下视图解析可省略)。 介绍了拦截器的使用(实现 HandlerInterceptor 接口 + 配置类)及与过滤器的区别
424 0
|
5月前
|
人工智能 安全 Java
Spring Boot 过滤器 拦截器 监听器
本文介绍了Spring Boot中的过滤器、拦截器和监听器的实现与应用。通过Filter接口和FilterRegistrationBean类,开发者可实现对请求和响应的数据过滤;使用HandlerInterceptor接口,可在控制器方法执行前后进行处理;利用各种监听器接口(如ServletRequestListener、HttpSessionListener等),可监听Web应用中的事件并作出响应。文章还提供了多个代码示例,帮助读者理解如何创建和配置这些组件,适用于构建更高效、安全和可控的Spring Boot应用程序。
621 0
|
8月前
|
缓存 安全 Java
深入解析HTTP请求方法:Spring Boot实战与最佳实践
这篇博客结合了HTTP规范、Spring Boot实现和实际工程经验,通过代码示例、对比表格和架构图等方式,系统性地讲解了不同HTTP方法的应用场景和最佳实践。
787 5
|
11月前
|
安全 Java 数据安全/隐私保护
springSecurity学习之springSecurity过滤web请求
通过配置 Spring Security 的过滤器链,开发者可以灵活地管理 Web 请求的安全性。理解核心过滤器的作用以及如何配置和组合这些过滤器,可以帮助开发者实现复杂的安全需求。通过具体的示例代码,可以清晰地了解 Spring Security 的配置方法和实践。
515 23
|
10月前
|
网络协议 Java Shell
java spring 项目若依框架启动失败,启动不了服务提示端口8080占用escription: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port-优雅草卓伊凡解决方案
java spring 项目若依框架启动失败,启动不了服务提示端口8080占用escription: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port-优雅草卓伊凡解决方案
647 7

热门文章

最新文章