能做什么?

网关是整个微服务API请求的入口,负责拦截所有请求,分发到服务上去。可以实现日志拦截、权限控制、解决跨域问题、限流、熔断、负载均衡、隐藏服务端的ip,黑名单与白名单拦截、授权等。

如何使用?
 

<!-- spring-cloud网关-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--Spring Webflux-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

如果不想启用gateway可以使用参数关闭 spring.cloud.gateway.enabled=false

重要概念

  • 路由:路由网关的基本构建块。它是由ID,目标URI,断言集合和过滤器集合定义。如果断言成功,则匹配路由
  • 断言:输入类型为ServerWebExchange。让你匹配http请求中的任何内容,例如请求参数等
  • 过滤器:GatewayFilter的实现。可以通过过滤器再发送给下游服务之前修改请求和响应。

工作原理

java白名单控制高权限 spring cloud gateway 白名单_java白名单控制高权限

处理流程:

  1. 请求过Gateway,首先Gateway Handler Mapping 断言
  2. 如果断言成功,通过Gateway Web Handler 去匹配路由
  3. 之后通过过滤器链,最后路由到目标服务

配置Gateway参数

简写配置

application.yml

spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: https://example.org
        predicates:
        - Cookie=mycookie,mycookievalue

翻译:断言Cookie中 key 为 mycookie的值为mycooievalue。这里注意,逗号后面是值,前面是key

 完全展开的配置

application.yml

spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: https://example.org
        predicates:
        - name: Cookie
          args:
            name: mycookie
            regexp: mycookievalue

翻译:和简写效果一致,只是不简写方式

 断言工厂

1. After 断言

spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: https://www.baidu.com
          predicates:
            - After=2022-06-04T14:15:00.000+08:00[Asia/Shanghai]

翻译:超过After后面的时间,断言才成功,跳转到baidu

2. Before断言

spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: https://www.baidu.com
          predicates:
            - Before=2022-06-04T14:15:00.000+08:00[Asia/Shanghai]

翻译:在之前的时间访问,断言才成功,跳转到baidu 

3. Between 断言

spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: https://www.baidu.com
          predicates:
            - Between=2022-06-04T14:15:00.000+08:00[Asia/Shanghai],2022-06-04T15:15:00.000+08:00[Asia/Shanghai]

 翻译:在这两个时间之间才能断言成功,可以 在抢购场景使用

4. Cookie 断言

spring:
  cloud:
    gateway:
      routes:
      - id: cookie_route
        uri: https://example.org
        predicates:
        - Cookie=chocolate, ch.p

翻译:访问时,携带cookie 并且 有个叫chocolate的key,value为ch.p,断言为true

5. Header 断言

spring:
  cloud:
    gateway:
      routes:
      - id: header_route
        uri: https://example.org
        predicates:
        - Header=X-Request-Id, \d+

翻译:必须携带请求头,并且有一个key为X-Request-Id,value为1个或者多个数字,断言为ture

6. Host 断言

spring:
  cloud:
    gateway:
      routes:
        - id: host_route
          uri: https://www.baidu.com
          predicates:
            - Host=localhost:8080

翻译:网关是本地起的并且端口是8080,如果拦截域名(xiaoyi.com),可以使用Host=xiaoyi.com,如果想拦截二级域名也可以使用Host=**.xiaoyi.com。这里我想用localhost.** 拦截,不能成功

7. 请求方式断言

spring:
  cloud:
    gateway:
      routes:
      - id: method_route
        uri: https://example.org
        predicates:
        - Method=GET,POST

翻译:当请求方式为GET,POST,则断言成功

8. 请求路径(Path)断言

spring:
  cloud:
    gateway:
      routes:
      - id: path_route
        uri: https://example.org
        predicates:
        - Path=/red/{segment},/blue/{segment}

翻译:使用正则匹配请求路径,比如/red/1 or /red/blue or /blue/green都会被断言为true。

这里截断的变量可以在后续的filter使用,如这里的segment,可以通过下面代码获取

Map<String, String> uriVariables = ServerWebExchangeUtils.getPathPredicateVariables(exchange);

String segment = uriVariables.get("segment");

9. 请求参数断言

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: https://example.org
        predicates:
        - Query=green

翻译:请求参数有gree时,断言成功

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: https://example.org
        predicates:
        - Query=red, gree.

翻译:请求参数有red,并且red=gree. ,断言成功

10. RemoteAddr 断言

spring:
  cloud:
    gateway:
      routes:
      - id: remoteaddr_route
        uri: https://example.org
        predicates:
        - RemoteAddr=192.168.1.1/24

翻译:IP为192.168.1 开头的ip断言为true,这/24是子网掩码的写法。

11. 权重断言

spring:
  cloud:
    gateway:
      routes:
      - id: weight_high
        uri: https://weighthigh.org
        predicates:
        - Weight=group1, 8
      - id: weight_low
        uri: https://weightlow.org
        predicates:
        - Weight=group1, 2

翻译:group1中 80% 的概率 断言到 weight_high,20%的概率 断言到 weight_low

过滤器

1. AddRequestHeader 

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: https://example.org
        filters:
        - AddRequestHeader=X-Request-red, blue

翻译:为第一个下游请求中加入X-Request-red=blue的请求头

2. AddRequestParameter 

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_parameter_route
        uri: https://example.org
        filters:
        - AddRequestParameter=red, blue

翻译:为下游请求中加入请求参数 red=blue

3. AddResponseHeader 

spring:
  cloud:
    gateway:
      routes:
      - id: add_response_header_route
        uri: https://example.org
        filters:
        - AddResponseHeader=X-Response-Red, Blue

翻译:响应头中加入X-Response-Res=Blue

4. DedupeResponseHeader 

spring:
  cloud:
    gateway:
      routes:
      - id: dedupe_response_header_route
        uri: https://example.org
        filters:
        - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin

翻译:在网关 CORS 逻辑和下游逻辑都添加它们的情况下,这将删除重复值Access-Control-Allow-Credentials和响应标头。Access-Control-Allow-Origin

5. RewritePath 

spring:
  cloud:
    gateway:
      routes:
      - id: rewritepath_route
        uri: https://example.org
        predicates:
        - Path=/red/**
        filters:
        - RewritePath=/red(?<segment>/?.*), $\{segment}

 翻译:重写uri,如请求为/red/xxx,通过gateway的此过滤器之后 变为/xxx。

/red(?<segment>/?.*), $\{segment}   逗号前为源字符串,逗号后面为转换后的字符串

============================2022年6月6日15:31:55========

问题:

请求地址:http://localhost:9999/mtc-auth/sys/role/list

gateway相关路由配置:

routes:
        - id: mtc-auth
          uri: lb://mtc-auth
          predicates:
            - Path=/auth/**
          filters:
            - RewritePath=/auth/?(?<segment>.*), /$\{segment}

gateway启动文件(开启了服务发现功能)

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication

上面请求地址能正确访问到auth服务。为什么能正确转发呢?这块断言只写了Path=/auth/**

答:源码探究:

当启用服务发现功能时,会启用网关的GatewayDiscoveryClientAutoConfiguration的类,其中主要代码。

@Bean
	public DiscoveryLocatorProperties discoveryLocatorProperties() {
		DiscoveryLocatorProperties properties = new DiscoveryLocatorProperties();
		properties.setPredicates(initPredicates());
		properties.setFilters(initFilters());
		return properties;
	}
public static List<PredicateDefinition> initPredicates() {
		ArrayList<PredicateDefinition> definitions = new ArrayList<>();
		// TODO: add a predicate that matches the url at /serviceId?

		// add a predicate that matches the url at /serviceId/**
		PredicateDefinition predicate = new PredicateDefinition();
		predicate.setName(normalizeRoutePredicateName(PathRoutePredicateFactory.class));
		predicate.addArg(PATTERN_KEY, "'/'+serviceId+'/**'");
		definitions.add(predicate);
		return definitions;
	}

上面注释很清楚的说明:默认会条件一个断言条件: /serviceId/ 此处 serviceId 为服务名称。

替换服务名的过程 要追踪到 CachingRouteLocator 监听器  其中进入subscribe方法

@Override
	public void onApplicationEvent(RefreshRoutesEvent event) {
		try {
			fetch().collect(Collectors.toList()).subscribe(list -> Flux.fromIterable(list)
					.materialize().collect(Collectors.toList()).subscribe(signals -> {
						applicationEventPublisher
								.publishEvent(new RefreshRoutesResultEvent(this));
						cache.put(CACHE_KEY, signals);
					}, throwable -> handleRefreshError(throwable)));
		}
		catch (Throwable e) {
			handleRefreshError(e);
		}
	}

一直追踪到RouteDefinitionRouteLocator

@Override
	public Flux<Route> getRoutes() {
		Flux<Route> routes = this.routeDefinitionLocator.getRouteDefinitions()
				.map(this::convertToRoute);

		if (!gatewayProperties.isFailOnRouteDefinitionError()) {
			// instead of letting error bubble up, continue
			routes = routes.onErrorContinue((error, obj) -> {
				if (logger.isWarnEnabled()) {
					logger.warn("RouteDefinition id " + ((RouteDefinition) obj).getId()
							+ " will be ignored. Definition has invalid configs, "
							+ error.getMessage());
				}
			});
		}

		return routes.map(route -> {
			if (logger.isDebugEnabled()) {
				logger.debug("RouteDefinition matched: " + route.getId());
			}
			return route;
		});
	}

之后进入DiscoveryClientRouteDefinitionLocator的getRouteDefinitions方法

@Override
	public Flux<RouteDefinition> getRouteDefinitions() {

		...

		return serviceInstances.filter(instances -> !instances.isEmpty())
				.map(instances -> instances.get(0)).filter(includePredicate)
				.map(instance -> {
					RouteDefinition routeDefinition = buildRouteDefinition(urlExpr,
							instance);

					final ServiceInstance instanceForEval = new DelegatingServiceInstance(
							instance, properties);

					for (PredicateDefinition original : this.properties.getPredicates()) {
						PredicateDefinition predicate = new PredicateDefinition();
						predicate.setName(original.getName());
    
						for (Map.Entry<String, String> entry : original.getArgs()
								.entrySet()) {
                            //这里是替换服务名的地方
							String value = getValueFromExpr(evalCtxt, parser,
									instanceForEval, entry);
							predicate.addArg(entry.getKey(), value);
						}
						routeDefinition.getPredicates().add(predicate);
					}

					for (FilterDefinition original : this.properties.getFilters()) {
						FilterDefinition filter = new FilterDefinition();
						filter.setName(original.getName());
						for (Map.Entry<String, String> entry : original.getArgs()
								.entrySet()) {
							String value = getValueFromExpr(evalCtxt, parser,
									instanceForEval, entry);
							filter.addArg(entry.getKey(), value);
						}
						routeDefinition.getFilters().add(filter);
					}

					return routeDefinition;
				});
	}

直到上面注释的地方就是替换服务名的地方了。

===========================2022年9月13日08:54:31 补充=======================

开启兼容服务注册表中注册的服务创建路由

spring.cloud.gateway.discovery.locator.enabled=true

开启此属性,gateway会从配置好的服务注册中心拉取对应服务注册表。

结合上面创建的默认路由源码,可以看出开启此属性之后,即使不写路由配置规则也会生成默认的路由规则。

例如有服务名为xiaoyi-masterdata,会默认生成下面路由规则。

routes:
        - id: xiaoyi-masterdata
          uri: lb://xiaoyi-masterdata
          predicates:
            - Path=/xiaoyi-masterdata/**
          filters:
            - RewritePath=/xiaoyi-masterdata/?(?<segment>.*), /$\{segment}