【Hoxton.SR1版本】Spring Cloud Gateway之GlobalFilter全局过滤器

目录

一、简介

二、自定义全局过滤器

三、总结


一、简介

Spring Cloud Gateway根据作用范围划分为GatewayFilter和GlobalFilter,二者区别如下:

  • GatewayFilter : 需要通过spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过spring.cloud.default-filters配置在全局,全局过滤器作用于所有的路由,不需要单独配置,我们可以用它来实现很多统一化处理的业务需求,比如权限认证,IP访问限制等等。

public interface GatewayFilter extends ShortcutConfigurable {
    String NAME_KEY = "name";
    String VALUE_KEY = "value";

    Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
  • GlobalFilter : 全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。

public interface GlobalFilter {
    Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}

Spring Cloud Gateway自带的GlobalFilter实现类有很多,如下图:

上图中每一个GlobalFilter都作用在每一个router上,能够满足大多数的需求。但是如果遇到业务上的定制,可能需要编写满足自己需求的GlobalFilter。

二、自定义全局过滤器

下面我们通过两个案例说明如何自定义全局过滤器。

(一)、自定义全局过滤器 - 限流过滤器(Token令牌桶算法)

【a】pom.xml中加入如下依赖

<dependency>
            <groupId>com.github.vladimir-bukhtoyarov</groupId>
            <artifactId>bucket4j-core</artifactId>
            <version>4.4.1</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.9</version>
        </dependency>

【b】payment8001增加如下方法

 /**
     * 测试自定义全局过滤器
     */
    @GetMapping("/customGatewayGlobalFilter/{name}")
    public String customGatewayGlobalFilter(@PathVariable("name") String name) {
        return "hello, [customGatewayGlobalFilter] the name is :" + name + ", the server port is " + serverPort;
    }

【c】application.yml配置文件

server:
  port: 9528
spring:
  application:
    name: springcloud-gateway
  cloud:
    gateway:
      routes:
        ########################################【AddRequestHeader GatewayFilter添加请求头】######################################################
        - id: payment_service8001_gatewayAddRequestHeader  #路由ID
          uri: http://localhost:8001  #指定payment8001的访问地址,即匹配后提供服务的路由地址
          predicates:
            - Path=/gatewayAddRequestHeader/**
          filters:
            #将X-Request-red:blue头添加到所有匹配请求的下游请求头中
            - AddRequestHeader=X-Request-red, blue

        ########################################【AddRequestParameter GatewayFilter添加请求参数】######################################################
        - id: payment_service8001_gatewayAddRequestParameter #路由ID
          uri: http://localhost:8001  #指定payment8001的访问地址,即匹配后提供服务的路由地址
          predicates:
            - Path=/gatewayAddRequestParameter
          filters:  #过滤器(filters:过滤器,过滤规则)
            # 添加指定参数
            - AddRequestParameter=name, weishihuai

        ########################################【AddResponseHeader GatewayFilter添加响应头】######################################################
        - id: payment_service8001_gatewayAddResponseHeader #路由ID
          uri: http://localhost:8001  #指定payment8001的访问地址,即匹配后提供服务的路由地址
          predicates:
            - Path=/gatewayAddResponseHeader
          filters:  #过滤器(filters:过滤器,过滤规则)
            # 添加指定参数
            - AddResponseHeader=X-Response-Red, Blue

        ########################################【Hystrix GatewayFilter断路器】######################################################
        - id: payment_service8001_gatewayHystrixGatewayFilter #路由ID
          uri: http://localhost:8001  #指定payment8001的访问地址,即匹配后提供服务的路由地址
          predicates:
            - Path=/gatewayHystrixGatewayFilter
          filters:  #过滤器(filters:过滤器,过滤规则)
            - name: Hystrix
              args:
                name: fallbackcmd
                fallbackUri: forward:/fallback

        - id: payment_service8001_outerFallback-gatewayHystrix #路由ID
          uri: http://localhost:8001  #指定payment8001的访问地址,即匹配后提供服务的路由地址
          predicates:
            - Path=/gatewayOuterFallbackHystrixGatewayFilter
          filters:  #过滤器(filters:过滤器,过滤规则)
            - name: Hystrix
              args:
                name: fallbackcmd
                fallbackUri: forward:/gatewayFallback  #此处需要与下面- Path=/gatewayFallback对应
        - id: outer-gateway-fallback
          uri: http://localhost:8001
          predicates:
            #fallback时调用的方法 http://localhost:8001/gatewayFallback
            - Path=/gatewayFallback

        ########################################【PrefixPath  GatewayFilter路径前缀过滤器工厂】######################################################
        - id: payment_service8001_prefixPathGatewayFilter #路由ID
          uri: http://localhost:8001  #指定payment8001的访问地址,即匹配后提供服务的路由地址
          predicates:
            - Path=/prefixPathGatewayFilter
          filters:  #过滤器(filters:过滤器,过滤规则)
            #将把/api作为所有匹配请求的路径的前缀。因此,对/prefixPathGatewayFilter的请求将被发送到/api/prefixPathGatewayFilter
            - PrefixPath=/api

        ########################################【StripPrefix GatewayFilter Factory路径截取过滤器工厂】######################################################
        #如下配置表示,在访问localhost:9528/api/gatewayStripPrefix/**请求的,gateway网关将/api截取掉,请求分发到http://localhost:8001中去.
        - id: payment_service8001_gatewayStripPrefix #路由ID
          uri: http://localhost:8001  #指定payment8001的访问地址,即匹配后提供服务的路由地址
          predicates:
            # 转发地址格式为 uri/gatewayStripPrefix, /api 部分会被下面的过滤器给截取掉.
            - Path=/api/gatewayStripPrefix/**
          filters:  #过滤器(filters:过滤器,过滤规则)
            # 截取路径位数  即截取/api
            - StripPrefix=1

        ########################################【RewritePath GatewayFilter Factory路径截取过滤器工厂】######################################################
        #如下配置表示,在访问localhost:9528/api/rewritePathGatewayFilter/**请求的,gateway网关将路径重写为localhost:9528/rewritePathGatewayFilter,然后请求分发到http://localhost:8001中去.
        - id: payment_service8001_rewritePathGatewayFilter #路由ID
          uri: http://localhost:8001  #指定payment8001的访问地址,即匹配后提供服务的路由地址
          predicates:
            - Path=/api/rewritePathGatewayFilter
          filters:  #过滤器(filters:过滤器,过滤规则)
            - RewritePath=/api(?<segment>/?.*), $\{segment}

        ##########################################【测试自定义权限过滤器】#################################################
        - id: payment_service8001_customAuthFilter #路由ID
          uri: http://localhost:8001  #指定payment8001的访问地址,即匹配后提供服务的路由地址
          predicates:
            - Path=/customAuthFilter/**
          filters:  #过滤器(filters:过滤器,过滤规则)
            - CustomAuth

        ##########################################【自定义过滤器工厂】#################################################
        - id: payment_service8001_customGatewayFilterFactory #路由ID
          uri: http://localhost:8001  #指定payment8001的访问地址,即匹配后提供服务的路由地址
          predicates:
            - Path=/customGatewayFilterFactory/**
          filters:  #过滤器(filters:过滤器,过滤规则)
            - CustomRequestTime=true

        ##########################################【测试自定义全局过滤器】#################################################
        - id: payment_service8001_customGatewayGlobalFilter #路由ID
          uri: http://localhost:8001  #指定payment8001的访问地址,即匹配后提供服务的路由地址
          predicates:
            - Path=/customGatewayGlobalFilter/**

      discovery:
        locator:
          enabled: true   #开启从注册中心动态创建路由的功能,利用微服务名进行路由
eureka:
  instance:
    hostname: springcloud-gateway-service
  client:
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://springcloud-eureka7001.com:7001/eureka/,http://springcloud-eureka7002.com:7002/eureka/   #集群版Eureka注册中心


【d】自定义全局过滤器:实现GlobalFilter, Ordered接口

package com.wsh.springcloud.filter;

import com.alibaba.fastjson.JSON;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.Bucket4j;
import io.github.bucket4j.Refill;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @version V1.0
 * @ClassName: com.wsh.springcloud.filter.FluidControlGlobalGatewayFilter.java
 * @Description: 自定义全局过滤器 - 限流过滤器(Token令牌桶算法)
 * @author: weishihuai
 * @date: 2020/8/22 11:30
 */
@Component
public class FluidControlGlobalGatewayFilter implements GlobalFilter, Ordered {
    private static final Logger logger = LoggerFactory.getLogger(FluidControlGlobalGatewayFilter.class);
    /**
     * 桶的最大容量,即能装载Token令牌的最大数量
     */
    private int capacity = 5;
    /**
     * 每次Token补充量
     */
    private int refillTokens = 1;
    /**
     * 补充Token的时间间隔
     */
    private Duration duration = Duration.ofSeconds(1);

    private static final Map<String, Bucket> BUCKET_CACHE = new ConcurrentHashMap<>();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
        Bucket bucket = BUCKET_CACHE.computeIfAbsent(ip,
                k -> {
                    Refill refill = Refill.greedy(refillTokens, duration);
                    Bandwidth limit = Bandwidth.classic(capacity, refill);
                    return Bucket4j.builder().addLimit(limit).build();
                });
        logger.info("客户端IP地址: " + ip + ", 获取: " + bucket.getAvailableTokens());

        if (bucket.tryConsume(1)) {
            //获取token成功,执行下一个过滤器逻辑
            return chain.filter(exchange);
        } else {
            ServerHttpResponse response = exchange.getResponse();
            Map<String, Object> data = new HashMap<>();
            data.put("code", HttpStatus.NOT_ACCEPTABLE);
            data.put("msg", "超过访问次数!");
            byte[] datas = JSON.toJSONString(data).getBytes(StandardCharsets.UTF_8);
            DataBuffer buffer = response.bufferFactory().wrap(datas);
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
            return response.writeWith(Mono.just(buffer));
        }
    }

    @Override
    public int getOrder() {
        return -10;
    }
}

【e】测试

启动项目,浏览器访问:http://localhost:9528/customGatewayGlobalFilter/weishihuai

网关成功转发请求,下面我们疯狂点击浏览器刷新按钮,手动触发令牌桶的限制,可见,再次访问时,已经被被限制访问,如下图:

后台日志: 

2020-08-22 21:20:43.044  INFO 7320 --- [ctor-http-nio-3] c.w.s.f.FluidControlGlobalGatewayFilter  : 客户端IP地址: 0:0:0:0:0:0:0:1, 获取: 0

 

(二)、自定义全局过滤器 - IP白名单过滤

当GlobalFilter的逻辑比较多时,建议大家单独写一个GlobalFilter来处理,比如我们要实现对IP的访问限制,不在IP白名单中就不让调用的需求,只需要实现GlobalFilter, Ordered这两个接口就可以了。

【a】自定义全局过滤器:实现GlobalFilter, Ordered接口

package com.wsh.springcloud.filter;

import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @version V1.0
 * @ClassName: com.wsh.springcloud.filter.IpWhitelistGlobalGatewayFilter.java
 * @Description: 自定义全局过滤器 - IP白名单过滤
 * @author: weishihuai
 * @date: 2020/8/22 12:03
 */
@Component
public class IpWhiteListGlobalGatewayFilter implements GlobalFilter, Ordered {

    private static final Logger logger = LoggerFactory.getLogger(IpWhiteListGlobalGatewayFilter.class);

    /**
     * 白名单配置集合
     * 此处为了演示方便,实际中需要采取配置的方式去配置白名单
     */
    private static List<String> whiteList = new ArrayList<>();

    static {
        //localhost
        whiteList.add("0:0:0:0:0:0:0:1");
        //127.0.0.1
        whiteList.add("127.0.0.1");
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取发起请求的客户端IP地址
        String ipAddress = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();

        //判断是否在配置的白名单中,存在则放行;不存在则拦截
        if (whiteList.contains(ipAddress)) {
            //获取token成功,执行下一个过滤器逻辑
            return chain.filter(exchange);
        } else {
            logger.error("IP地址:{}未在白名单中,禁止访问!", ipAddress);
            ServerHttpResponse response = exchange.getResponse();
            Map<String, Object> data = new HashMap<>();
            data.put("code", HttpStatus.NOT_ACCEPTABLE);
            data.put("msg", "当前IP地址未在白名单中,禁止访问!");
            byte[] datas = JSON.toJSONString(data).getBytes(StandardCharsets.UTF_8);
            DataBuffer buffer = response.bufferFactory().wrap(datas);
            response.setStatusCode(HttpStatus.FORBIDDEN);
            response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
            return response.writeWith(Mono.just(buffer));
        }
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

【b】测试

浏览器访问:http://localhost:9528/customGatewayGlobalFilter/weishihuai

浏览器访问: 

如上,可见使用localhost或者127.0.0.1访问的时候,由于都在配置的IP白名单中,所以这些访问过滤器将会放行。然后我们继续访问:http://192.168.1.12:9528/customGatewayGlobalFilter/weishihuai

 

由于192.168.1.12不在IP白名单中,所以请求被禁止访问。

三、总结

以上就是关于在Gateway中如何自定义全局过滤器的总结,以上相关项目的代码我已经放在Gitee上,有需要的小伙伴可以去拉取进行学习:https://gitee.com/weixiaohuai/springcloud_Hoxton,由于笔者水平有限,如有不对之处,还请小伙伴们指正,相互学习,一起进步。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页
实付 19.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值