Excluding URLs for a Filter in a Spring Web Application – 在Spring Web应用程序中为过滤器排除URLs

最后修改: 2019年 10月 6日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Most web applications have a use-case of performing operations like request logging, validation, or authentication. And, what’s more, such tasks are usually shared across a set of HTTP endpoints.

大多数Web应用程序都有一个执行请求记录、验证或认证等操作的用例。而且,更重要的是,这种任务通常在一组HTTP端点之间共享

The good news is that the Spring web framework provides a filtering mechanism for precisely this purpose.

好消息是,Spring Web框架提供了一个过滤机制,正是为了这个目的。

In this tutorial, we’ll learn how a filter-style task can be included or excluded from execution for a given set of URLs.

在本教程中,我们将学习如何将一个过滤器式的任务纳入或排除在给定的URL集的执行之外

2. Filter for Specific URLs

2.对特定的URL进行过滤

Let’s say our web application needs to log some information about its requests, such as their paths and content types. One way to do this is by creating a logging filter.

假设我们的Web应用程序需要记录一些关于其请求的信息,例如它们的路径和内容类型。做到这一点的一个方法是创建一个日志过滤器。

2.1. Logging Filter

2.1.记录过滤器

First, let’s create our logging filter in a LogFilter class that extends the OncePerRequestFilter class and implements the doFilterInternal method:

首先,让我们在一个LogFilter类中创建我们的日志过滤器,该类扩展了OncePerRequestFilter类并实现doFilterInternal方法:

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
  FilterChain filterChain) throws ServletException, IOException {
    String path = request.getRequestURI();
    String contentType = request.getContentType();
    logger.info("Request URL path : {}, Request content type: {}", path, contentType);
    filterChain.doFilter(request, response);
}

2.1. Rule-in Filter

2.1.规则输入过滤器

Let’s assume that we need the logging task to be executed only for select URL patterns, namely /health, /faq/*. For this, we’ll register our logging filter using a FilterRegistrationBean such that it matches only the required URL patterns:

让我们假设,我们需要只对选定的URL模式执行日志任务,即/health/faq/*。为此,我们将使用FilterRegistrationBean来注册我们的日志过滤器,使其只与所需的URL模式相匹配。

@Bean
public FilterRegistrationBean<LogFilter> logFilter() {
    FilterRegistrationBean<LogFilter> registrationBean = new FilterRegistrationBean<>();
    registrationBean.setFilter(new LogFilter());
    registrationBean.addUrlPatterns("/health","/faq/*");
    return registrationBean;
}

2.2. Rule-out Filter

2.2.筛选器(Rule-out Filter

If we want to exclude URLs from executing the logging task, we can achieve this easily in two ways:

如果我们想把URL排除在执行日志任务之外,我们可以通过两种方式轻松实现。

  • For a new URL, ensure that it doesn’t match the URL patterns used by the filter
  • For an old URL for which logging was earlier enabled, we can modify the URL pattern to exclude this URL

3. Filter for All Possible URLs

3.对所有可能的URL进行过滤

We easily met our previous use case of including URLs in the LogFilter with minimal effort. However, it gets trickier if the Filter uses a wildcard (*) to match all the possible URL patterns.

我们以最小的努力轻松满足了之前的用例,即在LogFilter中包含URL。然而,如果Filter使用通配符(*)来匹配所有可能的URL模式,就会变得棘手。

In this circumstance, we’ll need to write the inclusion and exclusion logic ourselves.

在这种情况下,我们需要自己编写包含和排除的逻辑。

3.1. Custom Filter

3.1.自定义过滤器

Clients can send useful information to the server by using the request headers. Let’s say our web application is currently operational only in the United States, which means we don’t want to process the requests coming from other countries.

客户端可以通过使用请求头来向服务器发送有用的信息。假设我们的网络应用目前只在美国运行,这意味着我们不想处理来自其他国家的请求。

Let’s further imagine that our web application indicates the locale via an X-Country-Code request header. Consequently, each request comes with this information, and we have a clear case for using a filter.

让我们进一步设想一下,我们的网络应用通过X-Country-Code请求头来表示地域。因此,每个请求都有这个信息,我们就有了一个使用过滤器的明确案例。

Let’s implement a Filter that checks for the header, rejecting requests that don’t meet our conditions:

让我们实现一个Filter ,检查该头,拒绝不符合我们条件的请求。

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
      FilterChain filterChain) throws ServletException, IOException {

    String countryCode = request.getHeader("X-Country-Code");
    if (!"US".equals(countryCode)) {
        response.sendError(HttpStatus.BAD_REQUEST.value(), "Invalid Locale");
        return;
    }

    filterChain.doFilter(request, response);
}

3.2. Filter Registration

3.2 过滤器注册

To start with, let’s use the asterisk (*) wildcard to register our filter to match all possible URL patterns:

首先,l我们使用星号(*)通配符来注册我们的过滤器以匹配所有可能的URL模式:

@Bean
public FilterRegistrationBean<HeaderValidatorFilter> headerValidatorFilter() {
    FilterRegistrationBean<HeaderValidatorFilter> registrationBean = new FilterRegistrationBean<>();
    registrationBean.setFilter(new HeaderValidatorFilter());
    registrationBean.addUrlPatterns("*");
    return registrationBean;
}

At a later point in time, we can exclude the URL patterns which are not required to execute the task of validating the locale request header information.

在稍后的时间点,我们可以排除那些不需要执行验证locale请求头信息任务的URL模式。

4. URL Exclusion

4.URL排除法

In this section, we’ll learn how to exclude URLs for our customer Filter.

在本节中,我们将学习如何为我们的客户Filter排除URLs。

4.1. Naive Strategy

4.1.天真策略

Let’s again imagine that we have a web route at /health that can be used to do a ping-pong health check of the application.

让我们再次想象一下,我们在/health有一个web路由,可以用来对应用程序进行ping-pong健康检查。

So far, all requests will trigger our filter. As we can guess, it’s an overhead when it comes to our health check.

到目前为止,所有的请求都会触发我们的过滤器。正如我们可以猜到的,在我们的健康检查中,这是一个开销。

So, let’s simplify our /health requests by excluding them from the main body of our filter:

因此,让我们简化我们的/health请求,把它们从过滤器的主体中排除。

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
  FilterChain filterChain) throws ServletException, IOException {
    String path = request.getRequestURI();
    if ("/health".equals(path)) {
    	filterChain.doFilter(request, response);
    	return;
    }

    String countryCode = request.getHeader("X-Country-Code");
    // ... same as before
}

We must note that adding this custom logic within the doFilter method introduces coupling between the /health endpoint and our filter. As such, it’s not optimal since we could break the filtering logic if we change the health check endpoint without making a corresponding change inside the doFilter method.

我们必须注意,在doFilter方法中添加这个自定义逻辑会在/health端点和我们的过滤器之间引入耦合。因此,这不是最佳选择,因为如果我们改变健康检查端点而不在doFilter方法内做相应的改变,就会破坏过滤逻辑。

4.2. Using the shouldNotFilter Method

4.2.使用shouldNotFilter方法

With the previous approach, we introduced tight coupling between the URL exclusion and the task execution logic for the filter. One could inadvertently introduce a bug in one part while intending to make changes to the other part.

在以前的方法中,我们在URL排除和过滤器的任务执行逻辑之间引入了紧密的耦合。人们可能会在无意中引入一个部分的错误,而打算对另一个部分进行修改。

Instead, we can isolate the two sets of logic by overriding the shouldNotFilter method:

相反,我们可以通过覆盖shouldNotFilter方法来隔离两套逻辑。

@Override
protected boolean shouldNotFilter(HttpServletRequest request)
  throws ServletException {
    String path = request.getRequestURI();
    return "/health".equals(path);
}

As a result, the doInternalFilter() method honors the single responsibility principle:

因此,doInternalFilter()方法尊重单一责任原则

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
  FilterChain filterChain) throws ServletException, IOException {
    String countryCode = request.getHeader("X-Country-Code");
    // ... same as before
}

5. Conclusion

5.总结

In this tutorial, we’ve explored how to exclude URL pattern(s) from a servlet filter in a Spring Boot web application for two use cases, namely logging and request header validation.

在本教程中,我们探讨了如何在Spring Boot网络应用中的servlet过滤器中排除URL模式,以满足两种使用情况,即日志和请求头验证。

Moreover, we learned that it gets tricky to rule-out a specific set of URLs for a filter that uses a * wildcard for matching all possible URL patterns.

此外,我们了解到,对于一个使用*通配符来匹配所有可能的URL模式的过滤器来说,排除一组特定的URL变得很棘手。

As always, the complete source code for the tutorial is available over on GitHub.

一如既往,该教程的完整源代码可在GitHub上获取