Skip to content

自定义网关过滤器

请求进入网关会碰到三类过滤器:DefaultFilter、当前路由的过滤器、GlobalFilter。请求路由后,会将三者合并到一个过滤器链(集合)中,排序后依次执行每个过滤器。

过滤器排序规则:

(1)每一个过滤器都必须指定一个 int 类型的 order 值,order 值越小,优先级越高,执行顺序越靠前。

(2)GlobalFilter 通过实现 Ordered 接口,或者使用 @Order 注解来指定 order 值,由我们自己指定。

(3)路由过滤器和 defaultFilter 的 order 由 Spring 指定,默认是按照声明顺序从1递增。

(4)如果三种过滤器的 order 值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter 的顺序执行。

1. 自定义全局过滤器

实现 GlobalFilterOrdered 接口,具体需要实现的逻辑代码在filter方法中完成,并给过滤器设置执行顺序order值,可参考如下代码:

java
/**
 * 全局黑名单过滤器
 *
 * @author guoyd
 * @create 2019/7/31
 * @since 1.0.0
 */
@Component
@Slf4j
public class BlackListGlobalFilter implements GlobalFilter, Ordered {

    @Autowired
    IPListService ipListService;

    /**
     * 功能描述:
     * 〈黑名单全局过滤器〉
     *
     * @param exchange
     * @param chain
     * @return:reactor.core.publisher.Mono<java.lang.Void>
     * @since: 1.0.0
     * @Author:guoyd
     * @Date: 2019/7/31 17:05
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //黑名单启用开关
        if (ipListService.getListStatusCache(Constant.BLACK_LIST).equals(Constant.IP_ENABLE)) {
            Stream<BlackWhiteIpInfo> stream = ipListService.getIPModelCache();
            stream.forEach(
                    blackWhiteIpInfo -> {
                        IPModel ipModel = JSON.parseObject(blackWhiteIpInfo.getValue(), IPModel.class);
                        //nginx反向代理后,所有的remoteAddr实际上变成了nginx的地址,所以改为nginx自动填写ip到请求头中,此处重新获取ip
                        String address = Optional.ofNullable(exchange.getRequest().getHeaders().getFirst(Constant.REMOTE_ADDR_KEY))
                                .orElse(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
                        //将","分隔形式的ip段,进行拆分处理
                        List<IPModel> blackIpList = new LinkedList<>();
                        if (ipModel.getType().equals(Constant.BLACK_LIST)) {
                            List<String> ipSegments = Arrays.asList(ipModel.getIp().split(","));
                            ipSegments.forEach(ip -> {
                                IPModel ipModel1 = new IPModel();
                                ipModel1.setIp(ip);
                                ipModel1.setType(Constant.BLACK_LIST);
                                blackIpList.add(ipModel1);
                            });
                        }

                        try {
                            blackIpList.sort(BlackListGlobalFilter::ipCompare);
                        } catch (Exception e) {
                            log.error("异常:{}", e);
                        }
                        for (IPModel ipMod : blackIpList) {
                            if (IPUtils.ipMatch(ipMod.getIp(), address)) {
                                throw new CommonException().setCode(Constant.BLACK_IP_IN).setHttpStatus(HttpStatus.FORBIDDEN);
                            }
                        }
                    }
            );
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        //全局body体解析(order为MIN_VALUE)必须放在最前,所以这里+1
        return Constant.FILTER_ORDER_INTEGER_MIN;
    }
}

2. 自定义路由过滤器

此过滤器在使用时需要具体添加到某一个路由上才可以生效,继承抽象类 AbstractGatewayFilterFactory<C> 实现,此过滤器对应的实现类的类名必须是 XXXGatewayFilterFactory ,以 GatewayFilterFactory 结尾。自定义实现类中需要创建一个内部类 Config

用来接收页面所设置的具体参数值,路由过滤器中的order要想生效,不能直接实现Order接口,需要在类中添加一个实现 GatewayFilter 的内部类,然后此内部类去实现 Ordered 接口才可实现(具体实现的代码逻辑可参考如下代码)。自定义过滤器实现完成之后,需要使用@Bean注解完成一个磁过滤器的Bean实现。上述代码实现完成之后,在参数表中添加具体过滤器的配置数据,路由过滤器参数对应的是数据库中的 FILTER_PARAM_INFO 表,表中 FILTER_VISIBLE 控制过滤器是否在过滤器列表中显示, FILTER_CONFIG 字段中的JSON串是过滤器需要配置的参数,前端解析之后展示,此字段如何添加可参考已有的参数数据进行修改实现。过滤器的英文名称为实现类 XXXGatewayFilterFactoryGatewayFilterFactory 的名称XXX,表中数据添加完成之后即可使用。

java
//过滤器实现参考

/**
 * @Created on 2021/7/21 17:24
 * @Description
 * @Author: zhaozsa
 */
@Slf4j
public class InstanceLimiterGatewayFilterFactory extends AbstractGatewayFilterFactory<InstanceLimiterGatewayFilterFactory.Config> {


    /**
     * Key-Resolver key.
     */
//    public static final String KEY_RESOLVER_KEY = "keyResolver";
//
//    private static final String EMPTY_KEY = "____EMPTY_KEY__";

    private final RateLimiter defaultRateLimiter;

    private final KeyResolver defaultKeyResolver;

    /**
     * Switch to deny requests if the Key Resolver returns an empty key, defaults to true.
     */
    private boolean denyEmptyKey = true;

    /** HttpStatus to return when denyEmptyKey is true, defaults to FORBIDDEN. */
    private String emptyKeyStatusCode = HttpStatus.FORBIDDEN.name();

    public InstanceLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter,
                                               KeyResolver defaultKeyResolver) {
        super(InstanceLimiterGatewayFilterFactory.Config.class);
        this.defaultRateLimiter = defaultRateLimiter;
        this.defaultKeyResolver = defaultKeyResolver;
    }

    public KeyResolver getDefaultKeyResolver() {
        return defaultKeyResolver;
    }

    public RateLimiter getDefaultRateLimiter() {
        return defaultRateLimiter;
    }

    public boolean isDenyEmptyKey() {
        return denyEmptyKey;
    }

    public void setDenyEmptyKey(boolean denyEmptyKey) {
        this.denyEmptyKey = denyEmptyKey;
    }

    public String getEmptyKeyStatusCode() {
        return emptyKeyStatusCode;
    }

    public void setEmptyKeyStatusCode(String emptyKeyStatusCode) {
        this.emptyKeyStatusCode = emptyKeyStatusCode;
    }

    @SuppressWarnings("unchecked")
    @Override
    public GatewayFilter apply(InstanceLimiterGatewayFilterFactory.Config config) {
        return new InstanceFilter(config, defaultRateLimiter, defaultKeyResolver);
    }

    private <T> T getOrDefault(T configValue, T defaultValue) {
        return (configValue != null) ? configValue : defaultValue;
    }


    public static class Config implements HasRouteId {

        private KeyResolver keyResolver;

        private RateLimiter rateLimiter;

        private HttpStatus statusCode = HttpStatus.TOO_MANY_REQUESTS;

        private Boolean denyEmptyKey;

        private String emptyKeyStatus;

        private String routeId;

        public KeyResolver getKeyResolver() {
            return keyResolver;
        }

        public InstanceLimiterGatewayFilterFactory.Config setKeyResolver(KeyResolver keyResolver) {
            this.keyResolver = keyResolver;
            return this;
        }

        public RateLimiter getRateLimiter() {
            return rateLimiter;
        }

        public InstanceLimiterGatewayFilterFactory.Config setRateLimiter(RateLimiter rateLimiter) {
            this.rateLimiter = rateLimiter;
            return this;
        }

        public HttpStatus getStatusCode() {
            return statusCode;
        }

        public InstanceLimiterGatewayFilterFactory.Config setStatusCode(HttpStatus statusCode) {
            this.statusCode = statusCode;
            return this;
        }

        public Boolean getDenyEmptyKey() {
            return denyEmptyKey;
        }

        public InstanceLimiterGatewayFilterFactory.Config setDenyEmptyKey(Boolean denyEmptyKey) {
            this.denyEmptyKey = denyEmptyKey;
            return this;
        }

        public String getEmptyKeyStatus() {
            return emptyKeyStatus;
        }

        public InstanceLimiterGatewayFilterFactory.Config setEmptyKeyStatus(String emptyKeyStatus) {
            this.emptyKeyStatus = emptyKeyStatus;
            return this;
        }

        @Override
        public void setRouteId(String routeId) {
            this.routeId = routeId;
        }

        @Override
        public String getRouteId() {
            return this.routeId;
        }
    }


    public class InstanceFilter implements GatewayFilter, Ordered {

        private Config config;
        /**
         * Key-Resolver key.
         */
        public static final String KEY_RESOLVER_KEY = "keyResolver";

        private static final String EMPTY_KEY = "____EMPTY_KEY__";

        private RateLimiter defaultRateLimiter;

        private KeyResolver defaultKeyResolver;

        /**
         * Switch to deny requests if the Key Resolver returns an empty key, defaults to true.
         */
        private boolean denyEmptyKey = true;

        /** HttpStatus to return when denyEmptyKey is true, defaults to FORBIDDEN. */
        private String emptyKeyStatusCode = HttpStatus.FORBIDDEN.name();

        public KeyResolver getDefaultKeyResolver() {
            return defaultKeyResolver;
        }

        public RateLimiter getDefaultRateLimiter() {
            return defaultRateLimiter;
        }

        public boolean isDenyEmptyKey() {
            return denyEmptyKey;
        }

        public void setDenyEmptyKey(boolean denyEmptyKey) {
            this.denyEmptyKey = denyEmptyKey;
        }

        public String getEmptyKeyStatusCode() {
            return emptyKeyStatusCode;
        }

        public void setEmptyKeyStatusCode(String emptyKeyStatusCode) {
            this.emptyKeyStatusCode = emptyKeyStatusCode;
        }


        public InstanceFilter(Config config, RateLimiter defaultRateLimiter, KeyResolver defaultKeyResolver) {
            this.config = config;
            this.defaultRateLimiter = defaultRateLimiter;
            this.defaultKeyResolver = defaultKeyResolver;
        }

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            KeyResolver resolver = getOrDefault(config.keyResolver, defaultKeyResolver);
            RateLimiter<Object> limiter = getOrDefault(config.rateLimiter,
                    defaultRateLimiter);
            boolean denyEmpty = getOrDefault(config.denyEmptyKey, this.denyEmptyKey);
            HttpStatusHolder emptyKeyStatus = HttpStatusHolder
                    .parse(getOrDefault(config.emptyKeyStatus, this.emptyKeyStatusCode));
            return resolver.resolve(exchange).defaultIfEmpty(EMPTY_KEY)
                    .flatMap(key -> {
                        if (EMPTY_KEY.equals(key)) {
                            if (denyEmpty) {
                                setResponseStatus(exchange, emptyKeyStatus);
                                return exchange.getResponse().setComplete();
                            }
                            return chain.filter(exchange);
                        }
                        String routeId = config.getRouteId();
                        if (routeId == null) {
                            Route route = exchange
                                    .getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
                            routeId = route.getId();
                        }
                        return limiter.isAllowed(routeId, key).flatMap(response -> {
                            for (Map.Entry<String, String> header : response.getHeaders()
                                    .entrySet()) {
                                exchange.getResponse().getHeaders().add(header.getKey(),
                                        header.getValue());
                            }
                            if (response.isAllowed()) {
                                return chain.filter(exchange);
                            }
                            setResponseStatus(exchange, config.getStatusCode());

//                ServerHttpResponse originalResponse = exchange.getResponse();
//                DataBufferFactory bufferFactory = originalResponse.bufferFactory();
//                LimiterResponse limiterResponse = new LimiterResponse();
//                limiterResponse.setCode(HttpStatus.TOO_MANY_REQUESTS.value());
//                limiterResponse.setMsg("TOUCH OFF INSTANCE LIMITER");
                            log.debug("TOUCH OFF INSTANCE LIMITER");
//                String lastStr = JSON.toJSONString(limiterResponse);
//                byte[] uppedContent = lastStr.getBytes();
//                DataBuffer buffer = bufferFactory.wrap(uppedContent);
//                return exchange.getResponse().writeWith(Mono.just(buffer));
                            String[] strings = key.split("@");
                            throw new CommonException().setCode("429").setMsg("TOUCH OFF INSTANCE LIMITER").setHttpStatus(HttpStatus.TOO_MANY_REQUESTS);
//                throw new TooManyResultsException("TOUCH OFF INSTANCE LIMITER "+ strings[1]);
                        });
                    });
        }

        @Override
        public int getOrder() {
            return Constant.FILTER_ORDER_INSTANCE_LIMITER;
        }
    }

    public static boolean isJSON2(String str) {
        boolean result = false;
        try {
            Object obj = JSON.parse(str);
            result = true;
        } catch (Exception e) {
            result = false;
        }
        return result;
    }

    /**
     * @description:
     *        返回给前端的统一格式
     * @param
     * @return:
     */
    @Data
    private class LimiterResponse implements Serializable {
        private static final long serialVersionUID = 1L;

        private int code;

        private boolean success = false;

        private String msg = "";

        private Object data;
    }
}
java
/**
 **  bean初始化
 **/
@Configuration
@RibbonClients(defaultConfiguration = RibbonDefaultConfiguration.class)
@ComponentScan("com.dcits.jupiter.gateway")
@MapperScan("com.dcits.jupiter.gateway.repository.mybatis")
@AutoConfigureAfter(JupiterRibbonAutoConfiguration.class)
@SuppressWarnings("all")
public class JupiterConfiguration {
    @Bean
    public InstanceLimiterGatewayFilterFactory instanceLimiterGatewayFilterFactory(InstanceRedisRateLimiter defaultRateLimiter, KeyResolver defaultKeyResolver) {
        return new InstanceLimiterGatewayFilterFactory(defaultRateLimiter, defaultKeyResolver);
    }
}

过滤器参数表添加的数据参考:

sql
INSERT INTO "ENS_GATEWAY"."FILTER_PARAM_INFO"("FILTER_ID", "FILTER_NAME", "FILTER_CONFIG", "FILTER_COMMENT",
                                              "FILTER_VISIBLE")
VALUES ('28', 'InstanceLimiter',
        '[{"name":"custom-redis-rate-limiter.burstCapacity","desc":"令牌桶容量","placeholder":"10","comment":"令牌桶容量","type":"text","eg":"10"},{"name":"custom-redis-rate-limiter.replenishRate","desc":"令牌桶每秒恢复量","placeholder":"10","comment":"令牌桶恢复速率","type":"text","eg":"10"},{"name":"custom-redis-rate-limiter.key","desc":"key值","placeholder":"10","comment":"key值","type":"text","eg":"10"},{"name":"custom-redis-rate-limiter.value","desc":"value值","placeholder":"10","comment":"value值","type":"text","eg":"10"},{"name":"key-resolver","desc":"resolver的bean名称","placeholder":"#{@requestHeaderFixationKeyResolver}","comment":"resolver的bean名称","type":"text","eg":"resolver的bean名称"},{"name":"rate-limiter","desc":"限流器名称","placeholder":"#{@customRedisRateLimiter}","comment":"限流器名称","type":"text","eg":"限流器名称"}]',
        '实例限流过滤器', '0');