1.前言
有些時候我們需要在 Spring Boot Servlet Web 應用中宣告一些自定義的 Servlet Filter 來處理一些邏輯,比如簡單的權限系統、請求頭過濾、防止 XSS 攻擊等,本篇將講解如何在 Spring Boot 應用中宣告自定義 Servlet Filter 以及定義它們各自的作用域和順序,
2. 自定義 Filter
可能有人說宣告 Servlet Filter 不就是實作 Filter 介面嘛,沒有什么好講的!是的這個沒錯,但是很多時候我們并不想我們宣告的 Filter 作用于全部的請求,甚至當一個請求經過多個 Filter 時需要按照既定的順序執行,接下來我會一一講解如何實作以上的功能,
2.1 Filter 的宣告
在 Spring Boot 中 只需要宣告一個實作 javax.servlet.Filter 介面的 Spring Bean 就可以了,如下:
@Configurationpublic 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 規則為 /*,所以需要我們改進
2.2 實作 Filter 順序化
如果需要實作順序化,可以借助于 Spring 提供的 @Order 注解或者 Ordered 介面,這里有一個坑:如果使用 @Order 注解一定要注解標注到具體的類上, 為了方便 JavaConfig 風格的宣告,我們可以實作 OrderedFilter 介面,該介面是 Filter 介面和 Ordered 介面的復合體,最終上面的配置如下
@Configurationpublic class FilterConfig { @Bean
public OrderedFilter responseFilter() { return new ResponseFilter("/foo/*");
} @Bean
public OrderedFilter requestFilter() { return new RequestFilter("/foo/*");
}
}
Filter 執行的規則是 數字越小越先執行 ,跟之前 Bean 實體化的優先級是一致的,
2.3 自定義 Filter 作用域
實作了順序化之后我們來看看如何實作自定義 Filter 的作用域,我們先說一下思路:
通過
ServletRequest物件來獲取請求的URI,然后對 URI 進行 ANT 風格匹配,關于 ANT 風格可以參考我的這一篇文章, 匹配通過執行具體的邏輯,否則跳過該 Filter ,
這里非常適合抽象一個基類來把該流程固定下來,留一個抽象方法作為函式鉤子,只需要繼承基類實作該抽象方法鉤子就可以了, 為了保證順序執行基類我們依然實作了 OrderedFilter 介面,我們來定義基類:
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 邏輯,列印請求的 URI:
@Slf4jpublic 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 章節 提供的方法就可以了,
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@Slf4jpublic 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 要點
-
FilterRegistrationBean與Filter之間是一對一關系, -
如果存在多個
FilterRegistrationBean需要呼叫其setName(String name)為其宣告唯一名稱,否則只有第一個注冊成功的有效, -
如果需要保證呼叫順序可通過呼叫其
setOrder(int order)方法進行設定,
4. 總結
我們在本文中通過自定義和 Spring Boot 提供的兩種方式實作了使用自定義 Filter ,雖然 Spring Boot 提供的方式更加方便一些,但是自定義的方式更能體現你對面向物件理解和提高你的抽象能力,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/96461.html
標籤:其他
