Request.UserHostAddress记录IP地址问题(内网IP)


    今天迁移至阿里云后,出现了一个问题,有些站点记录的访问者IP全是阿里云的两个内网IP,在程序中是通过Request.UserHostAddress读取IP地址的,之前从没遇到过这个问题,很是奇怪。经过分析比较发现,出现这些问题的站点都是跑在使用了负载均衡的Web服务器上(使用负载均衡,也是这次迁移在性能上的一处提升),这个问题应该与负载均衡有关。
    今天迁移至阿里云后,出现了一个问题,有些站点记录的访问者IP全是阿里云的两个内网IP,而程序中是通过Request.UserHostAddress读取IP地址的,之前从没遇到过这个问题,很是奇怪。经过分析比较发现,出现这些问题的站点都是跑在使用了负载均衡的Web服务器上(使用负载均衡,也是这次迁移在性能上的一处提升),这个问题应该与负载均衡有关。
    于是,上阿里云网站的管理控制台查看了一下负载均衡的相应设置,发现了线索,见下图:
    
    原来负载均衡器在向Web服务器转发请求时,将真实IP存储在服务器变量X-Forwarded-For中,而Web服务器中执行的Request.UserHostAddress实际是从服务器变量REMOTE_ADDR中获取IP地址的值,由于收到的请求是负载均衡器转发过来的,所以REMOTE_ADDR中存储的是负载均衡器的IP。要解决这个问题,需要改为通过Request.ServerVariables["HTTP_X_FORWARDED_FOR"]读取。当知道这个原因时,条件反射地就想马上动手——将代码中的Request.UserHostAddress改为Request.ServerVariables["HTTP_X_FORWARDED_FOR"]。但是,准备动手时,有些犹豫了,要改的地方不少。。。开始是冲动占上风,这时懒惰开始向冲动发起进攻。
    最终懒惰战胜了冲动,冷静下来思考有没有更好的解决方法。很快就想到了,这个场景正是HTTP Module可以用武的地方,只要在HTTP Module中把ServerVariables["REMOTE_ADDR"]的值改为ServerVariables["HTTP_X_FORWARDED_FOR"]的值,不用改一行代码,Request.UserHostAddress就能获取到正确的IP。的确是一个更好的解决方法,于是问题变成了怎么写这样的HTTP Module?
    懒惰继续占着上风,去网上找找有没有现成的HTTP Module,还真有(X-Forwarded-For HTTP Module For IIS7),而且是鼎鼎大名的F5负载均衡器的生产厂商 F5 Networks, Inc. 的开发人员开发的,名叫F5XFFHttpModule,2009年发布的,基于ISAPI(非托管的)。原以为通过它就能解决问题,而残酷的现实是这个HTTP Module在我们的IIS上怎么也加载不了,而且会引起整个站点无法正常访问。又继续找,网上多数提到的还是这个HTTP Module,没找到更好的。
    找现成的HTTP Module的懒惰想法没能成行,但懒惰依然痴心不改,冒出了一个更加懒惰的想法——是不是可以不用另外安装专门的HTTP Module,用现有的Url Rewrite Module来解决这个问题呢(Web服务器已经安装有这个Module)?借助Url Rewrite Module修改ServerVariables["REMOTE_ADDR"]的值。根据这个懒惰的想法竟然很快在网上找到一篇博文——如何讓在Reverse Proxy 之後的網站正常運行(URL Rewrite),根据这篇博文成功地以懒惰的方式解决了问题。
    下面是具体的操作步骤:
    1. 如果IIS上没有安装Url Rewrite Module,安装它(下载地址);
    2. 在IIS根节点或某个站点中打开Url Rewrite Module;
    3. 在右侧的Actions中点击View Server Variables...
    
    4. 点击Add,添加名为REMOTE_ADDR的服务器变量
    
    5. Back to Rules,添加一条下图所示的规则
    
    这条规则的作用就是将每个请求中ServerVariables["REMOTE_ADDR"]的值替换为ServerVariables["HTTP_X_FORWARDED_FOR"] 的值。
    applicationHost.config中的对应配置如下:
    
<rewrite>
    <allowedServerVariables>
        <add name="REMOTE_ADDR" />
    </allowedServerVariables>
    <globalRules>
        <rule name="HTTP_X_Forwarded_For-to-REMOTE_ADDR" enabled="true">
            <match url=".*" />
            <serverVariables>
                <set name="REMOTE_ADDR" value="{HTTP_X_Forwarded_For}" />
            </serverVariables>
            <action type="None" />
            <conditions>
                <add input="{HTTP_X_Forwarded_For}" pattern="^$" negate="true" />
            </conditions>
        </rule>
    </globalRules>
</rewrite>

    移花接木,借花献佛,这就是我们找到的解决这个问题的最简单的方法。
    【注意事项】
    添加该URL重写规则会造成IIS内核模式缓存不工作,详见微软的坑:Url重写竟然会引起IIS内核模式缓存不工作。