SpringBoot實現Filter的兩種方式

前言

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 方法控制執行順序。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章