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(){};
// 配置信息
}
}