如何在APIM中基于X-Forwarded-For实现简洁的IP过滤策略?
Azure API Management 基于X-Forwarded-For的IP过滤优化方案
问题背景
Azure API Management的ip-filter策略仅支持检查请求源IP,无法直接校验X-Forwarded-For头;尝试使用check-header策略时,发现X-Forwarded-For头带有端口后缀(格式为<ip>:<port>),而该策略不支持自动移除端口的逻辑。现有自定义实现较为臃肿且依赖大型条件判断,需要更简洁优雅的方案,同时满足:
- 支持提前退出校验流程(无需冗余判断)
- 采用列表形式配置允许的IP地址(类似
check-header或ip-filter的直观配置方式)
现有实现代码
<inbound> <base /> <set-variable name="allowedIPs" value="192.192.192.192" /> <!-- Determine the IP address to check --> <set-variable name="clientIP" value="@(context.Request.Headers.GetValueOrDefault("X-Forwarded-For", context.Request.IpAddress))" /> <!-- The regular expression ^([\d\.]+) captures only the numeric part of the IP address (i.e., the part before any colon : that would indicate the port). --> <set-variable name="cleanClientIP" value="@(System.Text.RegularExpressions.Regex.Match((string)context.Variables["clientIP"], @"^([\d\.]+)").Value)" /> <!-- Function to check if the IP is allowed --> <choose> <when condition="@(context.Variables.GetValueOrDefault<string>("allowedIPs").Split(',').Contains(context.Variables.GetValueOrDefault<string>("cleanClientIP")))"> <!-- REQUEST IS OK, ADD FURTHER LOGIC HERE --> </when> <otherwise> <return-response> <set-status code="403" reason="Forbidden" /> <set-header name="Content-Type" exists-action="override"> <value>application/json</value> </set-header> <set-body>{ "statusCode": 403, "message": "Not authorized" }</set-body> </return-response> </otherwise> </choose> </inbound>
优化后的实现方案
以下方案采用类似check-header的列表配置形式,同时整合IP清洗与校验逻辑,实现提前退出:
<inbound> <base /> <!-- 自定义XFF IP校验,配置风格对齐check-header --> <choose> <when condition="@( // 1. 获取XFF头或默认请求IP var clientIp = context.Request.Headers.GetValueOrDefault("X-Forwarded-For", context.Request.IpAddress); // 2. 清洗IP:移除端口部分,仅保留IPv4地址 var cleanIp = System.Text.RegularExpressions.Regex.Match(clientIp, @"^([\d\.]+)").Value; // 3. 检查清洗后的IP是否在允许列表中 new[] { "192.192.192.192", "10.0.0.1", "172.16.0.0/24" }.Any(ip => // 支持单个IP和CIDR网段校验 System.Net.IPAddress.TryParse(cleanIp, out var parsedCleanIp) && (ip.Contains('/') ? System.Net.NetworkInformation.IPAddressHelper.IsInSubnet(parsedCleanIp, ip) : parsedCleanIp.ToString() == ip) ) )"> <!-- IP校验通过,执行后续逻辑 --> </when> <otherwise> <!-- 校验失败,直接返回403 --> <return-response> <set-status code="403" reason="Forbidden" /> <set-header name="Content-Type" exists-action="override"> <value>application/json</value> </set-header> <set-body>{ "statusCode": 403, "message": "Not authorized" }</set-body> </return-response> </otherwise> </choose> </inbound>
方案亮点
- 配置直观:允许IP列表采用数组形式定义,类似
check-header的<value>配置风格,便于维护 - 逻辑紧凑:将IP获取、清洗、校验整合到单个条件表达式中,避免冗余变量定义
- 提前退出:通过
choose分支直接在校验失败时返回403,无需多余流程 - 扩展支持:额外支持CIDR网段校验(如
172.16.0.0/24),提升实用性
如果需要更贴近check-header的标签式配置,可以结合APIM的策略片段封装成可复用组件:
<!-- 策略片段:validate-xff-ip.xml --> <validate-xff-ip failed-check-httpcode="403" failed-check-error-message="Not authorized"> <value>192.192.192.192</value> <value>10.0.0.1</value> <value>172.16.0.0/24</value> </validate-xff-ip>
在主策略中引用并实现逻辑:
<inbound> <base /> <include-fragment fragment-id="validate-xff-ip" /> <choose> <when condition="@( var clientIp = context.Request.Headers.GetValueOrDefault("X-Forwarded-For", context.Request.IpAddress); var cleanIp = System.Text.RegularExpressions.Regex.Match(clientIp, @"^([\d\.]+)").Value; // 从策略片段的<value>节点中读取允许列表 context.Policy.Elements.OfType<XmlElement>().First(e => e.Name == "validate-xff-ip") .GetElementsByTagName("value").Cast<XmlElement>().Select(v => v.InnerText).Any(ip => System.Net.IPAddress.TryParse(cleanIp, out var parsedCleanIp) && (ip.Contains('/') ? System.Net.NetworkInformation.IPAddressHelper.IsInSubnet(parsedCleanIp, ip) : parsedCleanIp.ToString() == ip) ) )"> </when> <otherwise> <return-response> <set-status code="@(context.Policy.Elements.OfType<XmlElement>().First(e => e.Name == "validate-xff-ip").GetAttribute("failed-check-httpcode"))" reason="Forbidden" /> <set-header name="Content-Type" exists-action="override"> <value>application/json</value> </set-header> <set-body>@{ var errorMsg = context.Policy.Elements.OfType<XmlElement>().First(e => e.Name == "validate-xff-ip").GetAttribute("failed-check-error-message"); return $"{{\"statusCode\": 403, \"message\": \"{errorMsg}\"}}"; }</set-body> </return-response> </otherwise> </choose> </inbound>
内容的提问来源于stack exchange,提问作者Max
相关产品推荐
相关产品推荐

