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(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 地址访问订单服务。如下图:
从上图可知,成功访问到订单服务。如果将请求地址改为 127.0.0.1 IP 地址发起请求,如下图:
从上图请求可知,请求失败,因为 IP 地址不匹配。详细信息如下图:
下面是 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(){}; // 配置信息 } }