Spring Boot 2 實戰:如何自定義 Servlet Filter

1.前言

有些時候咱們須要在 Spring Boot Servlet Web 應用中聲明一些自定義的 Servlet Filter 來處理一些邏輯。好比簡單的權限系統、請求頭過濾、防止 XSS 攻擊等。本篇將講解如何在 Spring Boot 應用中聲明自定義 Servlet Filter 以及定義它們各自的做用域和順序。html

2. 自定義 Filter

可能有人說聲明 Servlet Filter 不就是實現 Filter 接口嘛,沒有什麼好講的!是的這個沒錯,可是不少時候咱們並不想咱們聲明的 Filter 做用於所有的請求。甚至當一個請求通過多個 Filter 時須要按照既定的順序執行。接下來我會一一講解如何實現以上的功能。java

2.1 Filter 的聲明

在 Spring Boot 中 只須要聲明一個實現 javax.servlet.Filter 接口的 Spring Bean 就能夠了。以下:web

@Configuration
public class FilterConfig {


    @Bean
    public Filter requestFilter() {
        return (request, response, chain) -> {
            //todo your business
        };
    }

    @Bean
    public Filter responseFilter() {
        return (request, response, chain) -> {
            //todo your business
        };
    }

}

很是簡單不是嗎?可是這種方式沒法保證順序,並且做用於全部的請求,即攔截的 Ant 規則爲 /*。因此須要咱們改進spring

2.2 實現 Filter 順序化

若是須要實現順序化,能夠藉助於 Spring 提供的 @Order 註解或者 Ordered 接口。這裏有一個坑:若是使用 @Order 註解必定要註解標註到具體的類上。
爲了方便 JavaConfig 風格的聲明。咱們能夠實現 OrderedFilter 接口,該接口是 Filter 接口和 Ordered 接口的複合體,最終上面的配置以下springboot

@Configuration
public class FilterConfig {

    @Bean
    public OrderedFilter responseFilter() {
        return new ResponseFilter("/foo/*");
    }

    @Bean
    public OrderedFilter requestFilter() {
        return new RequestFilter("/foo/*");

    }

}

Filter 執行的規則是 數字越小越先執行 。跟以前 Bean 實例化的優先級是一致的。ide

2.3 自定義 Filter 做用域

實現了順序化以後咱們來看看如何實現自定義 Filter 的做用域。咱們先說一下思路:函數

經過 ServletRequest 對象來獲取請求的 URI,而後對 URI 進行 ANT 風格匹配,關於 ANT 風格能夠參考個人這一篇 文章
匹配經過執行具體的邏輯,不然跳過該 Filter

這裏很是適合抽象一個基類來把該流程固定下來,留一個抽象方法做爲函數鉤子,只須要繼承基類實現該抽象方法鉤子就能夠了。 爲了保證順序執行基類咱們依然實現了 OrderedFilter 接口,咱們來定義基類:this

package cn.felord.springboot.filters;

import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;

/**
 * The type Abstract filter bean.
 *
 * @author Felordcn
 * @since 11 :19
 */
public abstract class AbstractFilterBean implements OrderedFilter {
    private Set<String> urlPatterns = new LinkedHashSet<>();

    public AbstractFilterBean(String... urlPatterns) {
        Collections.addAll(this.urlPatterns, urlPatterns);
    }

    /**
     * 各自邏輯的函數鉤子
     *
     * @param request  the request
     * @param response the response
     */
    public abstract void internalHandler(ServletRequest request, ServletResponse response);

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
         // 進行ant匹配  true 執行具體的攔截邏輯 false  跳過
        if (this.antMatch(request)) {
            this.internalHandler(request, response);
        }
        chain.doFilter(request, response);
    }


    private boolean antMatch(ServletRequest request) {
        Set<String> urlPatterns = getUrlPatterns();

        if (!CollectionUtils.isEmpty(urlPatterns)) {
            //進行Ant匹配處理
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            String uri = httpServletRequest.getRequestURI();
            Optional<String> any = urlPatterns.stream().filter(s -> {
                AntPathMatcher antPathMatcher = new AntPathMatcher();
                return antPathMatcher.match(s, uri);
            }).findAny();

            return any.isPresent();
        }
        // 若是 沒有元素 表示所有匹配
        return true;
    }


    public Set<String> getUrlPatterns() {
        return urlPatterns;
    }
}

咱們來實現一個具體的 Filter 邏輯,打印請求的 URIurl

@Slf4j
public class RequestFilter extends AbstractFilterBean {

    public RequestFilter(String... urlPatterns) {
        super(urlPatterns);
    }

    @Override
    public void internalHandler(ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        log.info("request from {}", httpServletRequest.getRequestURI());
    }

    @Override
    public int getOrder() {
       // 定義本身的優先級
        return 0;
    }    
}

而後定義好其 urlPatterns 並將其註冊到 Spring IoC 容器中就好了,若是有多個並且但願按照必定的順序執行,遵循 2.2 章節 提供的方法就能夠了。spa

3. Spring Boot的機制

以上方式是咱們本身造的輪子。其實 Spring Boot 還提供了 Filter 註冊機制來實現順序執行和聲明做用域。 咱們上面的邏輯能夠改成:

package cn.felord.springboot.configuration;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 使用 Spring Boot 提供的註冊機制
 *
 * @author Felordcn
 * @since 14:27
 **/
@Configuration
@Slf4j
public class SpringFilterRegistrationConfig {


    @Bean
    public FilterRegistrationBean<Filter> responseFilter() {
        FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setName("responseFilter");
        registrationBean.setOrder(2);
        registrationBean.setFilter((request, response, chain) -> {
            HttpServletResponse servletResponse = (HttpServletResponse) response;
            log.info("response status {}", servletResponse.getStatus());
            chain.doFilter(request,response);
        });
        registrationBean.addUrlPatterns("/foo/*");
        return registrationBean;
    }

    @Bean
    public FilterRegistrationBean<Filter> requestFilter() {
        FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setName("requestFilter");
        registrationBean.setOrder(1);
        registrationBean.setFilter((request, response, chain) -> {
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            log.info("request from {}", httpServletRequest.getRequestURI());
            chain.doFilter(request,response);
        });
        registrationBean.addUrlPatterns("/foo/*");
        return registrationBean;
    }

}

3.1 要點

  • FilterRegistrationBeanFilter 之間是一對一關係。
  • 若是存在多個 FilterRegistrationBean 須要調用其 setName(String name) 爲其聲明惟一名稱,不然只有第一個註冊成功的有效。
  • 若是須要保證調用順序可經過調用其 setOrder(int order) 方法進行設置。

4. 總結

咱們在本文中經過自定義和 Spring Boot 提供的兩種方式實現了使用自定義 Filter ,雖然 Spring Boot 提供的方式更加方便一些,可是自定義的方式更能體現你對面向對象理解和提升你的抽象能力。但願多多關注,與往常同樣。經過關注公衆號: Felordcn 回覆 f01 獲取本文 DEMO

相關文章
相關標籤/搜索