前言
Filter 是 SpringBoot 裏面使用來做驗證授權和跨域配置的第一選擇,但是我們應該如何實現一個 Filter 對所有URL進行過濾?
使用註解方式實現:
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
@Slf4j
@WebFilter(filterName = "BusinessFilter", urlPatterns = {"/*"})
public class BusinessFilter implements Filter {
//可訪問域名
private static List<String> ALLOW_SUBDOMAIN_REGEX = null;
@Override
public void init(FilterConfig filterConfig) {
ALLOW_SUBDOMAIN_REGEX = new ArrayList<>();
ALLOW_SUBDOMAIN_REGEX.add("https?://localhost");
ALLOW_SUBDOMAIN_REGEX.add("https?://localhost:\\d+");
log.info("init EncodingFilter at:{}", LocalDateTime.now());
}
private boolean inCorsSubdomainWhiteList(String s) {
if (StringUtils.isEmpty(s)) {
return false;
}
for (String p : ALLOW_SUBDOMAIN_REGEX) {
if (Pattern.matches(p, s)) {
return true;
}
}
return false;
}
private void allowCorsSubdomain(HttpServletRequest request, HttpServletResponse response) {
boolean isOption = false;
if ("OPTIONS".equals(request.getMethod())) {
isOption = true;
}
String requestOrigin = request.getHeader("Origin");
response.setHeader("Access-Control-Expose-Headers", "x-carry-down,x-token");
if (inCorsSubdomainWhiteList(requestOrigin)) {
response.setHeader("Access-Control-Allow-Origin", requestOrigin);
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,Ice");
if (isOption) {
response.setHeader("Access-Control-Max-Age", "1728000");
response.setHeader("Content-Type", "text/plain charset=UTF-8");
response.setHeader("Content-Length", "0");
}
}
if (isOption) {
response.setStatus(HttpServletResponse.SC_OK);
}
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//配置編碼集
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
//解決跨域問題
allowCorsSubdomain(request, response);
//下面可以做自定義token校驗
filterChain.doFilter(new WrappedHttpServletRequest(request), response);
}
@Override
public void destroy() {
log.info("destroy EncodingFilter at:{}", LocalDateTime.now());
}
}
主要是通過 @WebFilter 配置需要過濾的URL校驗正則
通過 FilterRegistrationBean 註冊
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.cors.CorsConfiguration;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
public class RegexCorsConfiguration extends CorsConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(RegexCorsConfiguration.class);
private final List<Pattern> allowedOriginsRegex;
public RegexCorsConfiguration() {
allowedOriginsRegex = new ArrayList<>();
}
public RegexCorsConfiguration(CorsConfiguration other) {
this();
setAllowCredentials(other.getAllowCredentials());
setAllowedOrigins(other.getAllowedOrigins());
setAllowedHeaders(other.getAllowedHeaders());
setAllowedMethods(other.getAllowedMethods());
setExposedHeaders(other.getExposedHeaders());
setMaxAge(other.getMaxAge());
}
@Override
public void addAllowedOrigin(String origin) {
super.addAllowedOrigin(origin);
try {
allowedOriginsRegex.add(Pattern.compile(origin));
} catch (PatternSyntaxException e) {
LOGGER.warn("Wrong syntax for the origin {} as a regular expression. If this origin is not supposed to be a regular expression, just ignore this error.", origin);
}
}
@Override
public String checkOrigin(String requestOrigin) {
String result = super.checkOrigin(requestOrigin);
return result != null ? result : checkOriginWithRegularExpression(requestOrigin);
}
private String checkOriginWithRegularExpression(String requestOrigin) {
return allowedOriginsRegex.stream()
.filter(pattern -> pattern.matcher(requestOrigin).matches())
.map(pattern -> requestOrigin)
.findFirst()
.orElse(null);
}
@Override
public CorsConfiguration combine(CorsConfiguration other) {
if (other == null) {
return this;
}
CorsConfiguration config = new RegexCorsConfiguration(this);
config.setAllowedOrigins(combine(this.getAllowedOrigins(), other.getAllowedOrigins()));
config.setAllowedMethods(combine(this.getAllowedMethods(), other.getAllowedMethods()));
config.setAllowedHeaders(combine(this.getAllowedHeaders(), other.getAllowedHeaders()));
config.setExposedHeaders(combine(this.getExposedHeaders(), other.getExposedHeaders()));
Boolean allowCredentials = other.getAllowCredentials();
if (allowCredentials != null) {
config.setAllowCredentials(allowCredentials);
}
Long maxAge = other.getMaxAge();
if (maxAge != null) {
config.setMaxAge(maxAge);
}
return config;
}
private List<String> combine(List<String> source, List<String> other) {
if (other == null || other.contains(ALL)) {
return source;
}
if (source == null || source.contains(ALL)) {
return other;
}
Set<String> combined = new HashSet<>(source);
combined.addAll(other);
return new ArrayList<>(combined);
}
}
import com.aidynamic.kaipiao.base.config.RegexCorsConfiguration;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
@Bean
public FilterRegistrationBean corsFilter() {
RegexCorsConfiguration config = new RegexCorsConfiguration();
config.setAllowCredentials(true);
// 設置你要允許的網站域名,如果全允許則設爲 *
config.addAllowedOrigin("https?://localhost");
config.addAllowedOrigin("https?://localhost:\\d+");
// 如果要限制 HEADER 或 METHOD 請自行更改
config.addAllowedHeader("*");
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
// 配置需要過濾的url路徑
bean.setUrlPatterns("/*");
// 這個順序很重要哦,爲避免麻煩請設置在最前
bean.setOrder(0);
return bean;
}
}
使用 SpringBoot 自帶的一個過濾器 CorsFilter 實現的跨域配置,如果需要進行 token 驗證的話則需自定義實現一個 Filter 來進行注入。
注意:一個 FilterRegistrationBean 只能註冊一個 Filter,通過 setOrder 方法控制執行順序。