You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 07:54:58