Gateway RemoteAddr 路由断言工厂

RemoteAddr 路由断言工厂接收一个来源列表(来源列表至少需要一个参数),语法格式:

RemoteAddr=CIDR注释1,...,CIDR注释N

注意,来源列表是 CIDR 注释(IPv4 或 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,则该路由匹配。

什么是 CIDR 注释?

CIDR(Classless Inter-Domain Routing)是一种用于分配 IP 地址的方法,它将 IP 地址分为网络地址和主机地址两部分,通过前缀长度来表示网络地址的位数。

CIDR 注释是在 CIDR 地址中添加注释信息,以便更好地理解网络拓扑结构和 IP 地址分配情况。

CIDR 注释通常使用斜杠符号“/”后面跟着注释信息的形式,例如“192.168.1.0/24(内网)”。这个注释表示这个 CIDR 地址属于一个内部网络,网络地址为“192.168.1.0”,前缀长度为“24”。

修改远程地址的解析方式

默认情况下,RemoteAddr 路由断言工厂使用传入请求的远程地址。如果 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 云网关只能通过 HAProxy 访问,则应使用 1。如果在访问 Spring Cloud Gateway 之前需要两跳受信任基础架构,则应使用 2。

请看下面的标头值:

X-Forwarded-For: 0.0.0.1, 0.0.0.2, 0.0.0.3

以下 maxTrustedIndex 值会产生以下远程地址:

maxTrustedIndex

结果

[Integer.MIN_VALUE,0]

(无效,初始化过程中出现 IllegalArgumentException )。

1

0.0.0.3

2

0.0.0.2

3

0.0.0.1

[4, Integer.MAX_VALUE]

0.0.0.1

下面的示例展示了如何使用 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")
)

示例

将“Gateway 搭建网关服务”项目的配置文件 bootstrap.yml 内容使用如下配置替换:

server:
  # 网关端口
  port: 9000

spring:
  application:
    # 服务名称
    name: gateway-demo01
  cloud:
    # nacos
    nacos:
      discovery:
        server-addr: localhost:8848
        username: nacos
        password: nacos
        group: DEFAULT_GROUP

    # 网关路由配置
    gateway:
      # 网关路由配置
      routes:
        # 路由id,自定义,只要唯一集合
        - id: service-order
          # 路由的目标地址,lb 就是负载均衡,后面跟服务名
          # 需要集成 nacos 等服务注册中心
          uri: lb://service-order
          # 路由断言,也就是判断请求是否符合路由规则的条件
          predicates:
            # RemoteAddr 路由断言工厂接收来源列表
            - RemoteAddr=192.168.0.0/24

修改好配置后,重启网关服务,使用 192.168.0.105 IP 地址访问订单服务。如下图:

Gateway RemoteAddr 路由断言工厂

从上图可知,成功访问到订单服务。如果将请求地址改为 127.0.0.1 IP 地址发起请求,如下图:

Gateway RemoteAddr 路由断言工厂

从上图请求可知,请求失败,因为 IP 地址不匹配。详细信息如下图:

Gateway RemoteAddr 路由断言工厂

源码分析

下面是 RemoteAddr 路由断言工厂源码:

package org.springframework.cloud.gateway.handler.predicate;

//...
public class RemoteAddrRoutePredicateFactory extends AbstractRoutePredicateFactory<RemoteAddrRoutePredicateFactory.Config> {

    private static final Log log = LogFactory.getLog(RemoteAddrRoutePredicateFactory.class);

    public RemoteAddrRoutePredicateFactory() {
       super(Config.class);
    }

    @Override
    public ShortcutType shortcutType() {
       return GATHER_LIST;
    }

    @Override
    public List<String> shortcutFieldOrder() {
       return Collections.singletonList("sources");
    }

    @NotNull
    private List<IpSubnetFilterRule> convert(List<String> values) {
       List<IpSubnetFilterRule> sources = new ArrayList<>();
        for (String arg : values) {
            addSource(sources, arg);
        }
       return sources;
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        // 解析配置,生成匹配规则
        List<IpSubnetFilterRule> sources = convert(config.sources);

       return exchange -> {
          // 解析出远程地址
          InetSocketAddress remoteAddress = config.remoteAddressResolver.resolve(exchange);
          if (remoteAddress != null && remoteAddress.getAddress() != null) {
             String hostAddress = remoteAddress.getAddress().getHostAddress();
             String host = exchange.getRequest().getURI().getHost();

             if (log.isDebugEnabled() && !hostAddress.equals(host)) {
                log.debug("Remote addresses didn't match " + hostAddress + " != " + host);
             }
             // 逐一匹配远程地址
             for (IpSubnetFilterRule source : sources) {
                if (source.matches(remoteAddress)) {
                   return true;
                }
             }
          }

          return false;
       };
    }

    private void addSource(List<IpSubnetFilterRule> sources, String source) {
       if (!source.contains("/")) { // no netmask, add default
          source = source + "/32";
       }

       String[] ipAddressCidrPrefix = source.split("/",2);
       String ipAddress = ipAddressCidrPrefix[0];
       int cidrPrefix = Integer.parseInt(ipAddressCidrPrefix[1]);

       sources.add(new IpSubnetFilterRule(ipAddress, cidrPrefix, IpFilterRuleType.ACCEPT));
    }

    @Validated
    public static class Config {
       @NotEmpty
       private List<String> sources = new ArrayList<>();

       @NotNull
       private RemoteAddressResolver remoteAddressResolver = new RemoteAddressResolver(){};

       // 配置信息
    }
}
说说我的看法
全部评论(
没有评论
关于
本网站属于个人的非赢利性网站,转载的文章遵循原作者的版权声明,如果原文没有版权声明,请来信告知:hxstrive@outlook.com
公众号