springcloud getway网关之路由断言工厂篇

1. 背景

最近,需要提升系统安全性,市面上有很多款网关服务的技术方案,最终选择了Spring Cloud Gateway。Gateway是基于异步非阻塞模型上进行开发的,有springcloud团队开发。用来代替Zuul。

1.1 什么是API 网关?

是作为一个 API 架构,用来保护、增强和控制对于 API 服务的访问。API 网关是一个处于应用程序或服务(提供 REST API 接口服务)之前的系统,用来管理授权、访问控制和流量限制等,这样 REST API 接口服务就被 API 网关保护起来,对所有的调用者透明。因此,隐藏在 API 网关后面的业务系统就可以专注于创建和管理服务,而不用去处理这些策略性的基础设施。
API 网关都有哪些职能,能干嘛?

在这里插入图片描述

  • 反向代理
  • 鉴权
  • 流量控制
  • 熔断:重试、安全、
  • 日志监控/指标等等…
  • 可以不对外开放服务端,只对外开放网关

1.2 API 网关的分类与功能?

在这里插入图片描述

1.3 网关位置

微服务架构中,网关在哪里:(网关是 所有 微服务的 入口)
在这里插入图片描述

2. Spring Cloud Gateway工作机制

2.0 工作流程

在这里插入图片描述

  1. 客户端发送请求到网关(spring cloud gateway);
  2. 如果 网关处理映射(Handler Mapping)确定(determines)了,该请求匹配上了路由,
    那么这请求将会被送到网关web处理程序上。这个处理程序,通过一个特定请求过滤器链运行这个请求。
  3. 上图中,filtes之所以画虚线,是因为过滤器既可以在发送异步(代理)请求之前,也可以在之后运行逻辑。
  4. 执行所有的pre(预先)过滤器,这个代理请求完成了。在完成代理请求后,就会运行post(发布)这个过滤器逻辑。

注意:在没有端口的路由中定义的uri, HTTP和HTTPS uri的默认端口值分别为80和443。

  • Filter在"pre" 类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等
  • 在"post"类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。

核心逻辑:路由转发 + 执行过滤链

2.1 Spring Cloud Gateway 组成概念

Gateway断言工厂是Spring Cloud Gateway提供的一种机制,用于定义路由请求的匹配条件。通过使用不同的断言工厂,我们可以根据请求的不同属性(如请求路径、请求方法、请求头等)来匹配和过滤请求。

  • Route(路由)
    路由是构建网关的基本模块,它由ID,目标URL,一系列的断言和过滤器组成,如果断言为true则匹配该路由

  • Predicate(断言)
    参考的是java8的java.util.function.Predicate
    开发人员可以匹配HTTP请求中的所有内容(例如请求头或者请求参数),如果请求与断言想匹配则路由

  • Filter(过滤)
    指的是Spring框架中GatewayFilter的实例,使用过滤器,可以请求被路由器前或者之后对请求进行修改

在这里插入图片描述

2.2 Spring Cloud 内置Route Predicate工厂

spring Cloud Gateway的路由匹配 作为 SpringWebFlux 处理器 映射(HandlerMapping)的基础建设的一部分,包含许多内置的Route Predicate工厂。所有这些断言都匹配HTTP请求的不同属性。多路由断言工厂通过and组合。SpringCloud Gateway包括许多内置(built-in)路由谓语工厂。所有这些谓语都匹配HTTP请求的不同属性。你可以用逻辑和句子与多个路由断言combine(结合)起来。
官方提供的路由工厂:

在这里插入图片描述
这些断言工厂的配置方式,参照官方文档:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.0.RELEASE/single/spring-cloud-gateway.html

在这里插入图片描述

2.4 和 Zuul网关对比

Zuul1基于servlet阻塞I/O的API Gateway,且进入维护
Zuul2虽然基于非阻塞I/O,但是还没有发布,且SpringCloud没有整合

getway优缺点和作用

优点

基于Spring Framework5, Project Reactor 和SpringBoot 2.0 进行构建
动态路由:能够匹配任何请求属性,可以对路由指定Predicate(断言) 和 Filter(过滤器)
继承Hystrix的断路器功能
集成SpringCloud服务发现功能
易于编写的Predicate(断言) 和 Filter(过滤器)
请求限流功能
支持路由重写

为什么使用Getway

SpringCloud Getway 使用的 WebFlux 框架实现的,而 WebFlux 框架底层使用了 高性能的 Reactor 模式通信框架 Netty;(异步非阻塞,性能高)

Spring Cloud Gateway 可以看做是一个 Zuul 1.x 的升级版和代替品,比 Zuul 2 更早的使用 Netty 实现异步 IO,从而实现了一个简单、比 Zuul 1.x 更高效的、与 Spring Cloud 紧密配合的 API 网关。

Spring Cloud Gateway 里明确的区分了 Router 和 Filter,并且一个很大的特点是内置了非常多的开箱即用功能,并且都可以通过 SpringBoot 配置或者手工编码链式调用来使用。

比如内置了 10 种 Router,使得我们可以直接配置一下就可以随心所欲的根据 Header、或者 Path、或者 Host、或者 Query 来做路由

比如区分了一般的 Filter 和全局 Filter,内置了 20 种 Filter 和 9 种全局 Filter,也都可以直接用。当然自定义 Filter 也非常方便。

作用

可以不对外开放服务端,只对外开放网关

3 使用

3.1 模型搭建

3.1.2. pom引入Gateway

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

在网关中,主要还是新增了gateway的依赖,不需要springboot的web和actuator,不要引入web依赖,不然会报错,项目都起不来。如果引用在pom中移除即可

3.1.3. 将网关注册进eureka/Zookeeper/Consul

和配置中心使用配置如下

server:
  port: 9527

spring:
  application:
    name: springcloud-gateway
eureka:
  instance:
    hostname: springcloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

3.1.4. 由yml网关配置。在yml中加入

spring: 
  cloud:
    gateway:
      routes:
        - id: payment_routh #路由的id,没有固定规则但要求唯一,建议配合服务名
        -url: http://localhost:8001 #匹配后提供服务的路由地址
        -predicates:
          - Path=/payment/get/** #断言,路径相匹配的进行路由

        - id: payment_routh2 #路由的id,没有固定规则但要求唯一,建议配合服务名
        -url: http://localhost:8001 #匹配后提供服务的路由地址
        -predicates:
          - Path=/payment/1b/** #断言,路径相匹配的进行路由

3.1.5 Java代码配置网关

3.5.1 . 用代码配置,创建一个配置类
@Configuration
public class GatwayConfig{
  @Bean
  public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
    RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
    
    routes .route("path_route",r -> r.path("/guonei").uri("http://baidu.com/guonei")).build();
    return routes.build();
  }
}

启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class GatewayMain {
    public static void main(String[] args) {
        SpringApplication.run(GatewayMain.class, args);
    }
}

3.1.6. 测试

在网关中,主要还是新增了gateway的依赖,不需要springboot的web和actuator,不要引入web依赖,不然会报错,项目都起不来。如果引用在pom中移除即可

  • 启动eureka/Zookeeper/Consul、网关、服务端
  • 通过网关访问服务端

在这里插入图片描述
可以看到网关Gateway成功的注册了注册中心,那网关如何做映射呢?那我们以springcloud-provider-payment8001生产者服务为例来映射一下。打开PaymentControler控制层,对get方法进行演示,因为我们之前访问都是http://localhost:8001/payment/get/1的访问,但是我们访问的时候是将我们的端口8001,暴露在了外边,不安全,但是我们不想暴露真的8001端口号,希望在外面8001外边包上一层我们的Gateway的端口9527,这就需要在yml配置新增路由配置。如下:

server:
  port: 9527

spring:
  application:
    name: springcloud-gateway
  cloud:
    gateway:
      routes:
        - id: payment_route # 路由的id,没有规定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001
          predicates:
            - Path=/payment/get/** # 断言,路径相匹配的进行路由

eureka:
  instance:
    hostname: springcloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

然后我们访问http://localhost:9527/payment/get/1,可以成功访问,说明我们的网关的路由和断言就配置成功啦!如下图:
在这里插入图片描述
我们现在配置的是YML进行配置的,还有一种配置方案就是通过硬编码的方式。就是代码中注入RouteLocator的Bean,是为了解决YML文件配置太多,文件太大的问题。那就开始撸起来吧!我们只要演示通过9527网关访问到外网的百度新闻网址。
新建一个config配置文件,如下:

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 配置了一个id为routr-name的路由规则
 * 当访问地址http://localhost:9527/guonei时会自动转发到http://news.baidu.com/guonei
 */
@Configuration
public class GateWayConfig {
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        routes.route("patn_route_buba", r -> r.path("/guonei")
             .uri("http://news.baidu.com/guonei")).build();
        return routes.build();
    }
}

配置完毕,重新启动服务,我们访问http://localhost:9527/guonei,可以成功转发到如下界面,说明我们GateWay通过编码的方式进行路由的映射配置。
在这里插入图片描述

3.2 实现动态路由功能

看我们的YML配置文件,我们配置的是http://localhost:8001是写死的,但是在我们微服务中生产者服务是不可能有一台机器的,所以说必须要进行负载均衡的配置。
实现负载均衡需要此依赖 - 后续yml配置会以 lb(动态路由协议) 开头
默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由功能

server:
  port: 9527
spring:
  application:
    name: springcloud-gateway
  cloud:
    gateway:
        # locator需要打开,不然通过 lb://.. 方式请求不到
      locator:
        enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名称进行路由
      routes:
        - id: payment_route # 路由的id,没有规定规则但要求唯一,建议配合服务名
          #匹配后提供服务的路由地址
          #uri: http://localhost:8001
          #lb是一个动态路由协议,后面的SPRINGCLOUD-PAYMENT-SERVICE是要跳转的服务名称。
          uri: lb://SPRINGCLOUD-PAYMENT-SERVICE
          predicates:
            - Path=/payment/get/** # 断言,路径相匹配的进行路由
eureka:
  instance:
    hostname: springcloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

为了测试负载均衡,我们需要启动Eureka7001,Eureka7002启动生产者服务8001和8002,启动Gateway9527服务,要记得在8002服务中也加上返回端口的get方法。访问http://localhost:9527/payment/get/1成功的实现了负载均衡。测试如下:
在这里插入图片描述
此处遇到个问题,lb在eureka中查找SPRINGCLOUD-PAYMENT-SERVICE这个服务名下的实例时,我8001没有将ip显示出来,访问http://localhost:9527/payment/get/1就一直报错,8002显示出来,访问http://localhost:9527/payment/get/1就成功了(默认是轮询,轮到8002成功,8001报错)
在这里插入图片描述
在这里插入图片描述
由此看出,用名称查找依赖服务发现,8001,8002主启动类加上@EnableDiscoveryClient注解,8001,8002yml文件加上prefer-ip-address: true #访问路径可以显示IP地址即可.

就这样终于成功访问到8001,8002,实现了负载均衡,美滋滋。。。。。

3.3 有两种配置谓语和过滤器:快捷方式和完全扩展的参数。

下面的大多数例子,都是用快捷方式。

3.2.1 . shortcut configuration 快捷配置

快捷配置由过滤器名称识别,后面跟着等号(=),输入参数值。参数值用逗号(,)隔开。

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

上面的样例 使用两个参数定义了cookie路由谓语工厂,也就是cookie 名称,mycookie,和匹配mycookievalue的值。

3.2.2.Fully Expanded Arguments 完全扩展参数

完全扩展参数发生在更多像是 带有键值对的 标准yaml配置。

(typically)通常,这将有name的key和args的key。这个args的key是为了配置谓语或者过滤器的 一个键值对的map(映射)。

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

上面显示的就是 Cookie断言的 快捷方式配置的完整配置。

4 断言

4.1 为什么要使用断言

断言在软件开发中起到了非常重要的作用。下面是一些使用断言的主要原因:

4.2 调试和开发:

断言可以用于验证程序的正确性,并在出现错误时提供有用的错误信息。通过在关键代码段中插入断言,开发人员可以检查假设是否成立,以及在遇到不符合预期的情况时立即发现问题。

4.3 防御性编程:

断言可以帮助开发人员在代码中插入一些额外的检查,以确保程序在运行时的各种情况下都能正确执行。这有助于防止潜在的错误和异常情况,并提高代码的健壮性和可靠性。

4.4文档和可读性:

断言可以作为代码的自文档,提供了一种清晰明了的方式来描述代码的预期行为和假设。这使得其他开发人员能够更容易地理解和维护代码。

4.5 测试:

断言是单元测试的重要组成部分。通过在测试中使用断言,可以验证代码的正确性,并确保它按照预期的方式工作。断言还可以帮助捕获和报告测试中的错误和异常情况。

总之,断言是一种强大的工具,可以帮助开发人员提高代码的质量和可靠性。它们可以帮助我们在开发和测试过程中发现和解决问题,以及提供更好的代码文档和可读性。

5 路由谓语断言工厂

Spring Cloud Gateway可以匹配各种路由,而其内部就包括许多内置的路由断言工厂。所有这些断言都匹配HTTP请求的不同属性。您可以将多个路由断言工厂与逻辑和语句组合在一起使用。

5.1 路由后断言工厂(The After Route Predicate Factory)

路由后断言工厂可以接受一个datetime的时间参数。此断言匹配发生在指定日期时间之后的请求。配置示例如下:

spring:
  cloud:
    gateway:
      routes:
      - id: after_route   # 路由 Id,唯一
        uri: https://www.baidu.com    # 目标 URI, 路由到微服务的地址
        predicates:       # 断言(判断条件)
        - After=2022-01-20T17:42:47.789-07:00[America/Denver]     # 采用的是路由后断言工厂,配置时间

配置解读: 设置某个时间点,等当前时间在该条件之后就可以进行访问该路由,即访问该服务地址。

注意:

  • 如果有多个满足条件的after配置,则会路由到第一个uri。

  • 参数格式,满足ZonedDateTime格式,

5.2 路由前断言工厂(The Before Route Predicate Factory)

路由断言工厂接受一个参数,即datetime。这个工厂其实跟前一个介绍的工厂效果恰恰是相反,也就是断言匹配发生在指定日期时间之前的请求。配置示例如下:

spring:
  cloud:
    gateway:
      routes:
      - id: before_route   # 路由 Id,唯一
        uri: https://example.org      # 目标 URI, 路由到微服务的地址
        predicates:       # 断言(判断条件)
        - Before=2017-01-20T17:42:47.789-07:00[America/Denver]    # 使用路由前断言工厂,配置时间

配置解读: 设置某个时间点,等当前时间在该条件之前都可以进行访问该路由,即访问该服务地址。

5.3 间隔路由断言工厂(The Between Route Predicate Factory)

这个路由工厂的作用顾名思义就是设置在一个时间范围内允许访问该服务地址,配置事例如下:

spring:
  cloud:
    gateway:
      routes:
      - id: between_route   # 路由 Id,唯一
        uri: https://example.org      # 目标 URI, 路由到微服务的地址
        predicates:
        - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]      # 使用间隔路由断言工厂,配置时间范围

5.4 请求头Cookie路由断言工厂(The Cookie Route Predicate Factory)

通过cookie和一个正则表达式作为断言条件的路由工厂,只要满足该条件就可以访问到该地址。

spring:
  cloud:
    gateway:
      routes:
      - id: between_route   # 路由 Id,唯一
        uri: https://example.org      # 目标 URI, 路由到微服务的地址
        predicates:
        - Cookie=chocolate, ch.p   # 使用Cookie路由断言工厂,配置cookie,正则表达式(可有可无)

配置解读:此路由将匹配具有一个名为chocolate的cookie的请求,该cookie的值匹配ch.p正则表达式。

5.5 请求头Header路由断言工厂(The Header Route Predicate Factory)

请求头路由断言工厂接受两个参数,报头名称和一个正则表达式。该断言与具有给定名称的头匹配,该名称的值与正则表达式匹配。下面的例子配置了:

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

配置解读:如果请求有一个名为X-Request-Id的请求头,其值匹配\d+正则表达式(也就是说,它有一个或多个数字的值),则此路由匹配。

5.6 域名路由断言工厂(The Host Route Predicate Factory)

主机路由断言工厂接受一个参数:主机名模式列表。该模式是一个ant样式的模式,通过 " . " 作为分隔符。这个断言匹配模式匹配的是Host头。配置示例如下:

spring:
  cloud:
    gateway:
      routes:
      - id: between_route   # 路由 Id,唯一
        uri: https://example.org      # 目标 URI, 路由到微服务的地址
        predicates:
        - Host=**.somehost.org,**.anotherhost.org     #使用主机路由断言工厂

配置解读:

URI模板变量(例如{sub}.myhost.org)也被支持。

如果请求的Host报头值为www.somehost.org或beta.somehost.org或www.anotherhost.org,则此路由匹配。

这个谓词提取URI模板变量(例如sub,在前面的例子中定义)作为名称和值的映射,并将其与在ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE中定义的键放在ServerWebExchange.getAttributes()中。这些值可以被GatewayFilter工厂使用。

5.7 Method方法路由断言工厂 (The Method Route Predicate Factory)

方法路由断言工厂接受一个方法参数,它是一个或多个参数:要匹配的HTTP方法。配置示例如下:

spring:
  cloud:
    gateway:
      routes:
      - id: between_route   # 路由 Id,唯一
        uri: https://example.org      # 目标 URI, 路由到微服务的地址
        predicates:
        - Method=GET,POST    #使用方法路由断言工厂 

配置解读:如果请求方法是GET或POST,则此路由匹配。

5.8 Path路由断言工厂(The Path Route Predicate Factory)

路径路由断言工厂接受两个参数:一个Spring PathMatcher模式列表和一个名为matchTrailingSlash的可选标志(默认为true)。配置示例如下:

spring:
  cloud:
    gateway:
      routes:
      - id: between_route   # 路由 Id,唯一
        uri: https://example.org      # 目标 URI, 路由到微服务的地址
        predicates:
        - Path=/red/{segment},/blue/{segment}    #使用路径路由断言工厂

配置解读:

如果请求路径符合以上断言要求,则由该路由匹配,例如: /red/1 或 /red/1/ 或 /red/blue或 /blue/green。

如果将matchTrailingSlash设置为false,那么请求路径/red/1/将不会被匹配。

这个断言提取URI模板变量(例如在前面的例子中定义的segment)作为名称和值的映射,并将其与在ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE中定义的键放在ServerWebExchange.getAttributes()中。这些值可以被GatewayFilter工厂使用。

可以使用一个实用方法(称为get)来简化对这些变量的访问。下面的例子展示了如何使用get方法:

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

5.9 查询路由断言工厂 (The Query Route Predicate Factory)

查询路由断言工厂接受两个参数:一个必需的参数和一个可选的regexp(它是一个Java正则表达式)。配置示例如下:

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

配置解读:如果请求包含green查询参数,则上述路由匹配。

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

配置解读:如果请求中包含一个red的查询参数,且该参数的值匹配gree. 正则表达式(即green,gree2都是可以的),则上述路由匹配。,所以red和greet是匹配的。

5.10 根据请求IP的地址路由断言工厂(The RemoteAddr Route Predicate Factory)

根据请求的远程地址进行匹配,可以使用 IP 地址或 IP 地址段。
远程地址路由断言工厂接受一个源列表(最小大小为1),它是 CIDR-notation (IPv4 or IPv6) ,例如192.168.0.1/16(其中192.168.0.1是一个IP地址,16是一个子网掩码)。配置示例如下:

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

配置解读:如果请求的远端地址是,例如192.168.1.10,则此路由匹配。

请求头REMOTE_ADDR 详情

表示发出请求的客户端主机的 IP 地址,但它的值不是由客户端提供的,而是Nginx与客户端进行TCP连接过程中,获得的客户端的真实地址 IP 地址,REMOTE_ADDR 无法伪造,因为建立 TCP 连接需要三次握手,如果伪造了源 IP,无法建立 TCP 连接,更不会有后面的 HTTP 请求。

当你的浏览器访问某个网站时:

  • 假设中间没有任何代理,那么网站的Web服务器(Nginx,Apache等)获取的remote_addr为你的机器IP。
  • 如果你用了某个代理,那么你的浏览器会先访问这个代理,然后再由这个代理转发到网站,这样Web服务器获取的remote_addr为代理机器的IP。

修改远端地址解析方式

默认情况下,远程地址路由断言工厂使用来自传入请求的远程地址。如果Spring Cloud Gateway位于代理层后面,这可能与实际的客户端IP地址不匹配。

你可以通过设置一个自定义的RemoteAddressResolver来自定义远程地址解析的方式。Spring Cloud Gateway提供了一个非默认的远程地址解析器,它基于X-Forwarded-For报头,即XForwardedRemoteAddressResolver。
XForwardedRemoteAddressResolver有两个静态构造函数方法,它们采用不同的安全方法:

  • XForwardedRemoteAddressResolver::trustAll返回一个RemoteAddressResolver,它总是采用X-Forwarded-For头中发现的第一个IP地址。这种方法很容易受到欺骗,因为恶意客户端可能会为X-Forwarded-For设置初始值,解析器将接受该初始值。
  • XForwardedRemoteAddressResolver::maxTrustedIndex获取一个与Spring Cloud Gateway前面运行的可信基础设施数量相关的索引。例如,如果Spring Cloud Gateway只能通过HAProxy访问,那么应该使用值1。如果在访问Spring Cloud Gateway之前需要信任基础设施的两个跃点,那么应该使用值2。
    考虑以下报头值:
X-Forwarded-For: 0.0.0.1, 0.0.0.2, 0.0.0.3

下面的maxTrustedIndex值产生以下远程地址:

在这里插入图片描述
下面的例子展示了如何用Java实现相同的配置:

RemoteAddressResolver resolver = XForwardedRemoteAddressResolver
    .maxTrustedIndex(1);
 
...
 
.route("direct-route",
    r -> r.remoteAddr("10.1.1.1", "10.10.1.1/24")
        .uri("https://downstream1")
.route("proxied-route",
    r -> r.remoteAddr(resolver, "10.10.1.1", "10.10.1.1/24")
        .uri("https://downstream2")
)

5.11 Weight 权重路由断言工厂(The Weight Route Predicate Factory)

根据请求的权重进行路由,用于实现负载均衡,这个权重是按组计算的。权重路由断言工厂接受两个参数:group和Weight(一个int类型)。权重按每组计算。配置示例如下:

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

配置解读:将把80%的流量转发到weighthigh.org, 20%的流量转发到weighlow.org

5.12 The XForwarded 远程地址路由断言工厂(The XForwarded Remote Addr Route Predicate Factory)

The XForwarded 远程地址路由断言工厂接受一个源列表(最小大小为1),它它是 CIDR-notation (IPv4 or IPv6) ,例如192.168.0.1/16(其中192.168.0.1是一个IP地址,16是一个子网掩码)。

这个路由断言允许基于X-Forwarded-For HTTP头对请求进行过滤。

这可以用于反向代理,如负载均衡器或web应用程序防火墙,在这些反向代理中,只有当请求来自由这些反向代理使用的可信IP地址列表时,才应该允许该请求。
配置示例如下:

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

配置解读:如果X-Forwarded-For报头包含192.168.1.10,则此路由匹配。

HTTP 请求头中的 X-Forwarded-For 简介

X-Forwarded-For:记录请求到达服务器经过的代理设备ip

X-Forwarded-For 是一个 HTTP 扩展头部。HTTP/1.1(RFC 2616)协议并没有对它的定义,它最开始是由 Squid 这个缓存代理软件引入,用来表示 HTTP 请求端真实 IP。如今它已经成为事实上的标准,被各大 HTTP 代理、负载均衡等转发服务广泛使用,并被写入 RFC 7239(Forwarded HTTP Extension)标准之中。X-Forwarded-For 请求头格式非常简单,就这样:

X-Forwarded-For: client, proxy1, proxy2

可以看到,XFF 的内容由「英文逗号 + 空格」隔开的多个部分组成,最开始的是离服务端最远的设备 IP,然后是每一级代理设备的 IP。

如果一个 HTTP 请求到达服务器之前,经过了三个代理 Proxy1、Proxy2、Proxy3,IP 分别为 IP1、IP2、IP3,用户真实 IP 为 IP0,那么按照 XFF 标准,服务端最终会收到以下信息:

X-Forwarded-For: IP0, IP1, IP2

Proxy3 直连服务器,它会给 XFF 追加 IP2,表示它是在帮 Proxy2 转发请求。列表中并没有 IP3,IP3 可以在服务端通过 Remote Address 字段获得。我们知道 HTTP 连接基于 TCP 连接,HTTP 协议中没有 IP 的概念,Remote Address 来自 TCP 连接,表示与服务端建立 TCP 连接的设备 IP,在这个例子里就是 IP3。

Remote Address 无法伪造,因为建立 TCP 连接需要三次握手,如果伪造了源 IP,无法建立 TCP 连接,更不会有后面的 HTTP 请求。不同语言获取 Remote Address 的方式不一样,例如 php 是 $_SERVER[“REMOTE_ADDR”],Node.js 是 req.connection.remoteAddress,但原理都一样。
参考:X-Forwarded-For

5.1.3 自定义Gateway断言工厂

除了使用内置的断言工厂,我们还可以自定义Gateway断言工厂来满足特定的需求。自定义断言工厂需要实现 org.springframework.cloud.gateway.handler.predicate.GatewayPredicateFactory 接口,并注册为Spring Bean。
以下是一个自定义Gateway断言工厂的示例:

@Component
public class CustomPredicateFactory implements GatewayPredicateFactory<CustomPredicateFactory.Config> {

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        // 自定义断言逻辑
        return exchange -> {
            // 实现自定义的匹配逻辑
            return true; // 返回匹配结果
        };
    }

    @Override
    public Config newConfig() {
        return new Config();
    }

    @Override
    public Class<Config> configClass() {
        return Config.class;
    }

    public static class Config {
        // 自定义配置参数
    }
}

在上述示例中,我们定义了一个名为 CustomPredicateFactory 的自定义断言工厂。通过实现 apply 方法,我们可以定义自己的断言逻辑。通过实现 newConfig 方法,我们可以定义自己的配置参数。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值