How to add a filter class in Spring Boot?

2019-01-01 12:42发布

问题:

I wonder, if there is any annotation for a Filter class (for web applications) in Spring Boot? Perhaps @Filter?

I want to add a custom filter in my project.

The Spring Boot Reference Guide mentioned about FilterRegistrationBean, but I am not sure how to use it.

回答1:

If you want to setup a third-party filter you can use FilterRegistrationBean. For example the equivalent of web.xml

<filter>
     <filter-name>SomeFilter</filter-name>
        <filter-class>com.somecompany.SomeFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>SomeFilter</filter-name>
    <url-pattern>/url/*</url-pattern>
    <init-param>
       <param-name>paramName</param-name>
       <param-value>paramValue</param-value>
    </init-param>
</filter-mapping>

These will be the two beans in your @Configuration file

@Bean
public FilterRegistrationBean someFilterRegistration() {

    FilterRegistrationBean registration = new FilterRegistrationBean();
    registration.setFilter(someFilter());
    registration.addUrlPatterns(\"/url/*\");
    registration.addInitParameter(\"paramName\", \"paramValue\");
    registration.setName(\"someFilter\");
    registration.setOrder(1);
    return registration;
} 

public Filter someFilter() {
    return new SomeFilter();
}

The above was tested with spring-boot 1.2.3



回答2:

Here is an example of one method of including a custom filter in a Spring Boot MVC application. Be sure to include the package in a component scan:

package com.dearheart.gtsc.filters;

import java.io.IOException;

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.HttpServletResponse;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

@Component
public class XClacksOverhead implements Filter {

  public static final String X_CLACKS_OVERHEAD = \"X-Clacks-Overhead\";

  @Override
  public void doFilter(ServletRequest req, ServletResponse res,
      FilterChain chain) throws IOException, ServletException {

    HttpServletResponse response = (HttpServletResponse) res;
    response.setHeader(X_CLACKS_OVERHEAD, \"GNU Terry Pratchett\");
    chain.doFilter(req, res);
  }

  @Override
  public void destroy() {}

  @Override
  public void init(FilterConfig arg0) throws ServletException {}

}


回答3:

There isn\'t a special annotation to denote a servlet filter. You just declare a @Bean of type Filter (or FilterRegistrationBean). An example (adding a custom header to all responses) is in Boot\'s own EndpointWebMvcAutoConfiguration;

If you only declare a Filter it will be applied to all requests. If you also add a FilterRegistrationBean you can additionally specify individual servlets and url patterns to apply.

Note:

As of Spring Boot 1.4, FilterRegistrationBean is not deprecated and simply moved packages from org.springframework.boot.context.embedded.FilterRegistrationBean to org.springframework.boot.web.servlet.FilterRegistrationBean



回答4:

There are three ways to add your filter,

  1. Annotate your filter with one of the Spring stereotypes such as @Component
  2. Register a @Bean with Filter type in Spring @Configuration
  3. Register a @Bean with FilterRegistrationBean type in Spring @Configuration

Either #1 or #2 will do if you want your filter applies to all requests without customization, use #3 otherwise. You don\'t need to specify component scan for #1 to work as long as you place your filter class in the same or sub-package of your SpringApplication class. For #3, use along with #2 is only necessary when you want Spring to manage your filter class such as have it auto wired dependencies. It works just fine for me to new my filter which doesn\'t need any dependency autowiring/injection.

Although combining #2 and #3 works fine, I was surprised it doesn\'t end up with two filters applying twice. My guess is that Spring combines the two beans as one when it calls the same method to create both of them. In case you want to use #3 alone with authowiring, you can AutowireCapableBeanFactory. The following is an example,

private @Autowired AutowireCapableBeanFactory beanFactory;

    @Bean
    public FilterRegistrationBean myFilter() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        Filter myFilter = new MyFilter();
        beanFactory.autowireBean(myFilter);
        registration.setFilter(myFilter);
        registration.addUrlPatterns(\"/myfilterpath/*\");
        return registration;
    }


回答5:

UPDATE: 2017-12-16:

There are 2 simple ways to do this in Spring boot 1.5.8.RELEASE, no need for xml.

First way: If you do not have any spacific url pattern, you can use @Component like this: (Full code and details are here https://www.surasint.com/spring-boot-filter/)

@Component
public class ExampleFilter implements Filter{
   ...
}

Second way: If you want to use url patterns, you can use @WebFilter like this: (Full code and details are here https://www.surasint.com/spring-boot-filter-urlpattern/)

@WebFilter(urlPatterns = \"/api/count\")
public class ExampleFilter implements Filter{
 ...
}

But you also need to add @ServletComponentScan annotation in your @SpringBootApplication class:

@ServletComponentScan
@SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
...
}

Note that @Component is Spring\'s annotation, but @WebFilter is not. @WebFiler is Servlet 3 annotation.

Both ways, you just need basic spring boot dependency in pom.xml (no need for explicit tomcat embedded jasper)

    <?xml version=\"1.0\" encoding=\"UTF-8\"?>
<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.8.RELEASE</version>
    </parent>

    <groupId>com.surasint.example</groupId>
    <artifactId>spring-boot-04</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

WARNING: The first way, if the Controller in spring boot returns to a jsp file, the request will pass the filter twice.

While, in the second way, the request will pass the filter only once.

I prefer the second way because it is more similar to default behavior in Servlet specification (https://docs.oracle.com/cd/E19879-01/819-3669/6n5sg7b0b/index.html)

You can see more test log here https://www.surasint.com/spring-boot-webfilter-instead-of-component/



回答6:

Here is an example of my custom Filter class:

package com.dawson.controller.filter;

import org.springframework.stereotype.Component;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


@Component
public class DawsonApiFilter extends GenericFilterBean {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        if (req.getHeader(\"x-dawson-nonce\") == null || req.getHeader(\"x-dawson-signature\") == null) {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setContentType(\"application/json\");
            httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, \"Required headers not specified in the request\");
            return;
        }
        chain.doFilter(request, response);
    }
}

And I added it to the Spring boot configuration by adding it to Configuration class as follows:

package com.dawson.configuration;

import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
import com.dawson.controller.filter.DawsonApiFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

@SpringBootApplication
public class ApplicationConfiguration {
    @Bean
    public FilterRegistrationBean dawsonApiFilter() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new DawsonApiFilter());
// In case you want the filter to apply to specific URL patterns only
        registration.addUrlPatterns(\"/dawson/*\");
        return registration;
    }
}


回答7:

From Spring docs,

Embedded servlet containers - Add a Servlet, Filter or Listener to an application

To add a Servlet, Filter, or Servlet *Listener provide a @Bean definition for it.

Eg:

@Bean
public Filter compressFilter() {
    CompressingFilter compressFilter = new CompressingFilter();
    return compressFilter;
}

Add this @Bean config to your @Configuration class and filter will be registered on startup.

Also you can add Servlets, Filters, and Listeners using classpath scanning,

@WebServlet, @WebFilter, and @WebListener annotated classes can be automatically registered with an embedded servlet container by annotating a @Configuration class with @ServletComponentScan and specifying the package(s) containing the components that you want to register. By default, @ServletComponentScan will scan from the package of the annotated class.



回答8:

If you use Spring Boot + Spring Security, you can do that in the security configuration.

In the below example, I\'m adding a custom filter before the UsernamePasswordAuthenticationFilter (see all the default Spring Security filters and their order).

@EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired FilterDependency filterDependency;

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

And the filter class

class MyFilter extends OncePerRequestFilter  {
    private final FilterDependency filterDependency;

    public MyFilter(FilterDependency filterDependency) {
        this.filterDependency = filterDependency;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
        HttpServletResponse response,
        FilterChain filterChain)
        throws ServletException, IOException {
       // filter
       filterChain.doFilter(request, response);
    }
}


回答9:

Using the @WebFilter annotation, it can be done as follows:

@WebFilter(urlPatterns = {\"/*\" })
public class AuthenticationFilter implements Filter{

    private static Logger logger = Logger.getLogger(AuthenticationFilter.class);

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

    @Override
    public void doFilter(ServletRequest arg0, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

         logger.info(\"checking client id in filter\");
        HttpServletRequest request = (HttpServletRequest) arg0;
        String clientId = request.getHeader(\"clientId\");
        if (StringUtils.isNotEmpty(clientId)) {
            chain.doFilter(request, response);
        } else {
            logger.error(\"client id missing.\");
        }
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
        // TODO Auto-generated method stub

    }

}


回答10:

You can use @WebFilter javax.servlet.annotation.WebFilter on a class that implements javax.servlet.Filter

@WebFilter(urlPatterns = \"/*\")
public class MyFilter implements Filter {}

Then use @ServletComponentScan to register



回答11:

I saw a lot of answers here but I didn\'t try any of them. I\'ve just created the filter as in the following code.

import org.springframework.context.annotation.Configuration;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter(urlPatterns = \"/Admin\")
@Configuration
public class AdminFilter implements Filter{
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse  servletResponse, FilterChain filterChain) throws IOException, ServletException      {
    System.out.println(\"happened\");

    }

    @Override
    public void destroy() {

    }
}

And leaved the remaining Spring Boot application as it was.



回答12:

It\'s more an advice than answer, but if you are using a Spring MVC in your web application the good idea is to use Spring HandlerInterceptor instead of Filter

It can do the same job, but also - Can work with ModelAndView - Its methods can be called before and after request processing, or after request completion.
- It can be easily tested

1 Implement HandlerInterceptor interface and add a @Component annotation to your class

@Component
public class SecurityInterceptor implements HandlerInterceptor {

    private static Logger log = LoggerFactory.getLogger(SecurityInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        request.getSession(true);
        if(isLoggedIn(request))
            return true;

        response.getWriter().write(\"{\\\"loggedIn\\\":false}\");
        return false;
    }

    private boolean isLoggedIn(HttpServletRequest request) {
        try {
            UserSession userSession = (UserSession) request.getSession(true).getAttribute(\"userSession\");
            return userSession != null && userSession.isLoggedIn();
        } catch(IllegalStateException ex) {
            return false;
        }
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {

    }
}

2 Configure your Interceptor

@Configuration
public class WebConfig implements WebMvcConfigurer {

    private HandlerInterceptor securityInterceptor;

    @Autowired
    public void setSecurityInterceptor(HandlerInterceptor securityInterceptor) {
        this.securityInterceptor = securityInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(securityInterceptor).addPathPatterns(\"/**\").excludePathPatterns(\"/login\", \"/logout\");
    }

}


回答13:

you can also make a filter by using @WebFilter and implements Filter, it will do.

 @Configuration
        public class AppInConfig 
        {
        @Bean
      @Order(1)
      public FilterRegistrationBean aiFilterRegistration() {
            FilterRegistrationBean registration = new FilterRegistrationBean();
            registration.setFilter(new TrackingFilter());
            registration.addUrlPatterns(\"/**\");
            registration.setOrder(1);
            return registration;
        } 
    @Bean(name = \"TrackingFilter\")
        public Filter TrackingFilter() {
            return new TrackingFilter();
        }   
    }


回答14:

First, add @ServletComponentScan to your SpringBootApplication class.

@ServletComponentScan
public class Application {

Second, create a filter file extending Filter or third-party filter class and add @WebFilter to this file like this:

@Order(1) //optional
@WebFilter(filterName = \"XXXFilter\", urlPatterns = \"/*\",
    dispatcherTypes = {DispatcherType.REQUEST, DispatcherType.FORWARD},
    initParams = {@WebInitParam(name = \"confPath\", value = \"classpath:/xxx.xml\")})
public class XXXFilter extends Filter{


回答15:

@WebFilter(urlPatterns=\"/*\")
public class XSSFilter implements Filter {

    private static final org.apache.log4j.Logger LOGGER = LogManager.getLogger(XSSFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        LOGGER.info(\"Initiating XSSFilter... \");

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpRequestWrapper requestWrapper = new HttpRequestWrapper(req);
        chain.doFilter(requestWrapper, response);
    }

    @Override
    public void destroy() {
        LOGGER.info(\"Destroying XSSFilter... \");
    }

}

You need to implement Filter and need to be annotated with @WebFilter(urlPatterns=\"/*\")

And in Application or Configuration class you need to add @ServletComponentScan By this it your filter will get registered.



回答16:

I saw answer by @Vasily Komarov. Similar approach, but using abstract HandlerInterceptorAdapter class instead of using HandlerInterceptor.

Here is an example...

@Component
public class CustomInterceptor extends HandlerInterceptorAdapter {
   @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
    }
}

@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private CustomInterceptor customInterceptor ;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(customInterceptor );
    }

}


回答17:

Filters are mostly used in logger files it varies according to the logger you using in the project Lemme explain for log4j2:

<Filters>
                <!-- It prevents error -->
                <ThresholdFilter level=\"error\" onMatch=\"DENY\" onMismatch=\"NEUTRAL\"/>
                <!-- It prevents debug -->
                <ThresholdFilter level=\"debug\" onMatch=\"DENY\" onMismatch=\"NEUTRAL\" />
                <!-- It allows all levels except debug/trace -->
                <ThresholdFilter level=\"info\" onMatch=\"ACCEPT\" onMismatch=\"DENY\" /> 
            </Filters>

Filters are used to restrict the data and i used threshold filter further to restrict the levels of data in the flow I mentioned the levels that can be restricted over there. For your further refrence see the level order of log4j2 - Log4J Levels: ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF