SpringCloud Gateway 中过滤器中 GlobalFilter 介绍

2021-10-14 0 By admin

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

  1. GatewayFilter : 需要通过spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过spring.cloud.default-filters配置在全局,作用在所有路由上。
  2. GlobalFilter : 不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。

Spring Cloud Gateway 框架内置的Global Filter 如下:

Gateway Global Filter 全局过滤器
Gateway Global Filter 全局过滤器

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

一、自定义全局过滤器 TokenFilter 验证Token 过滤器

在下面的案例中将讲述如何编写自己GlobalFilter;该GlobalFilter会校验请求中是否包含了请求参数“token”,如何不包含请求参数“token”则不转发路由,否则执行正常的逻辑。

public class TokenFilter implements GlobalFilter, Ordered {
  Logger logger=LoggerFactory.getLogger( TokenFilter.class );
  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    String token = exchange.getRequest().getQueryParams().getFirst("token");
    if (token == null || token.isEmpty()) {
      logger.info( "token is empty..." );
      exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
      return exchange.getResponse().setComplete();
    }
    return chain.filter(exchange);
  }
  @Override
  public int getOrder() {
    return -100;
  }
}

在上面的 TokenFilter 需要实现 GlobalFilter 和 Ordered 接口,这和实现GatewayFilter很类似。
然后根据 ServerWebExchange 获取 ServerHttpRequest,然后根据ServerHttpRequest中是否含有参数token,如果没有则完成请求,终止转发;否则执行正常的逻辑。

1.1、将过滤器注入到 Ioc 容器

然后需要将TokenFilter在工程的启动类中注入到Spring Ioc容器中,代码如下:

@Bean
public TokenFilter tokenFilter(){
    return new TokenFilter();
}

1.2、验证测试

curl localhost:8081/customer/123
可以看到请没有被转发,请求被终止,并在控制台打印了如下日志:
2018-11-16 15:30:13.543 INFO 19372 --- [ctor-http-nio-2] gateway.TokenFilter : token is empty...
上面的日志显示了请求进入了没有传“token”的逻辑。

二、LoadBalancerClientFilter 负载均衡客户端过滤器

LoadBalancerClientFilter 在交换属性 GATEWAY_ REQUEST_ URL_ ATTR 中查找URL, 如果URL有一个 lb 前缀 ,即 lb:// myservice,将使用 LoadBalancerClient 将名称 解析为实际的主机和端口,如示例中的 myservice。
未修改的原始URL将保存到 GATEWAY_ ORIGINAL_ REQUEST_ URL_ ATTR 属性的列表中。过滤器还将查看 ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR属性以查看它是否等于lb,然后应用相同的规则。

spring:
  cloud:
    gateway:
      routes:
      - id: myRoute
        uri: lb://service
        predicates:
        - Path=/service/**
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
	URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
	String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
	
	if (url == null || (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
		return chain.filter(exchange);
	}
	//保留原始url
	addOriginalRequestUrl(exchange, url);

	log.trace("LoadBalancerClientFilter url before: " + url);
   //负载均衡到具体服务实例
	final ServiceInstance instance = choose(exchange);

	if (instance == null) {
		throw new NotFoundException("Unable to find instance for " + url.getHost());
	}

	URI uri = exchange.getRequest().getURI();

   //如果没有提供前缀的话,则会使用默认的'< scheme>',否则使用' lb:< scheme>' 机制。
	String overrideScheme = null;
	if (schemePrefix != null) {
		overrideScheme = url.getScheme();
	}
   //根据获取的服务实例信息,重新组装请求的 url
	URI requestUrl = loadBalancer.reconstructURI(new DelegatingServiceInstance(instance, overrideScheme), uri);
   // Routing 相关 的 GatewayFilter 会 通过 GATEWAY_ REQUEST_ URL_ ATTR 属性, 发起 请求。
	log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
	exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
	return chain.filter(exchange);
}

从过滤器执行方法中可以看出,负载均衡客户端过滤器的实现步骤如下:

  1. 构造函数传入负载均衡客户端,依赖中添加 Spring Cloud Netflix Ribbon 即可 注入 该 Bean。
  2. 获取请求的 URL 及其前缀,如果 URL 不为空且前缀为lb或者网关请求的前缀是 lb,则保存原始的URL,负载到具体的服务实例并根据获取的服务实例信息,重新组装请求的URL。
  3. 最后,添加请求的URL到GATEWAY_ REQUEST_ URL_ ATTR,并提交到过滤器链中继续执行

在组装请求的地址时,如果loadbalancer没有提供前缀的话,则使用默认的,即overrideScheme 为null,否则的话使用 lb: