Rails使用负载均衡后获取用户ip错误问题解决

网站的流量越来越大后开始使用负载均衡来提高网站的并发数,负载均衡有很多选择,可以使用现成的slb产品,也可以使用nginx进行代理转发流量,使用后发现一个问题,服务器上获取的用户ip变成负载均衡机器的ip了,这里记录一下这个问题的解决。

个人原创,版权所有,转载请注明出处,并保留原文链接:
https://www.embbnux.com/2016/07/10/rails_get_remote_ip_after_use_slb_load_balancer/

一、Ruby on Rails获取用户ip的方法

研究了下源码,发现rails的controller action里获取用户ip调用的是request.remote_ip,这个方法直接从action_dispatch.remote_ip拿到用户ip, 而action_dispatch.remote_ip是由中间件ActionDispatch::RemoteIp所得到,主要代码如下:

  class RemoteIp
    attr_reader :check_ip, :proxies
    def initialize(app, ip_spoofing_check = true, custom_proxies = nil)
      @app = app
      @check_ip = ip_spoofing_check
      @proxies = if custom_proxies.blank?
        TRUSTED_PROXIES
      elsif custom_proxies.respond_to?(:any?)
        custom_proxies
      else
        Array(custom_proxies) + TRUSTED_PROXIES
      end
    end

    def call(env)
      req = ActionDispatch::Request.new env
      req.remote_ip = GetIp.new(req, check_ip, proxies)
      @app.call(req.env)
    end

    class GetIp
      def initialize(req, check_ip, proxies)
        @req      = req
        @check_ip = check_ip
        @proxies  = proxies
      end

      def calculate_ip
        remote_addr = ips_from(@req.remote_addr).last

        client_ips    = ips_from(@req.client_ip).reverse
        forwarded_ips = ips_from(@req.x_forwarded_for).reverse

        should_check_ip = @check_ip && client_ips.last && forwarded_ips.last
        if should_check_ip && !forwarded_ips.include?(client_ips.last)
          raise IpSpoofAttackError, "IP spoofing attack?! " +
            "HTTP_CLIENT_IP=#{@req.client_ip.inspect} " +
            "HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
        end

        ips = [forwarded_ips, client_ips, remote_addr].flatten.compact

        # If every single IP option is in the trusted list, just return REMOTE_ADDR
        filter_proxies(ips).first || remote_addr
      end

      protected

      def filter_proxies(ips)
        ips.reject do |ip|
          @proxies.any? { |proxy| proxy === ip }
        end
      end
    end
  end
  

可以看到对于有代理的请求的ip获取是通过获取请求头中的X_FORWARDED_FOR的数据得到的,按理来说,nginx里面配置:

  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  

就没问题了,rails会自动获取到用户的真实ip. 这里发现获取得到的ip变为代理服务器的ip,会出现问题只能是出现在filter_proxies(ips).first这一步,filter_proxies这一步把ips列表里面的可信任ip给去掉,像127.0.0.1, 以及10.1.1.1等这样的内网ip去掉,得到的第一个ip被认为是用户ip. 问题就出现在我这边用的复杂均衡器的ip不是常见的10开头的内网ip,是100开头的神奇ip。所以要解决就得把该机器的ip放到custom_proxies里面,这样这个ip也会在filter_proxies这一步被去掉。

二、解决问题

现在关键是怎么把负载均衡机器的ip加到custom_proxies里,参看rails里调用RemoteIp的地方发现:

middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies

所以只要在初始化的时候就上下面这句话就ok了:

  Rails.application.config.action_dispatch.trusted_proxies = %w(100.10.0.0/16).map { |proxy| IPAddr.new(proxy) }

三、题外话之中间件

不得不佩服rails的中间件的设计思想,真的很超前。最近写koa2,感觉中间件的设计使代码清晰了好多,提前预告下,下一篇博客就讲一下怎么用rails的思维来写koa。

《Rails使用负载均衡后获取用户ip错误问题解决》有1个想法

发表评论

电子邮件地址不会被公开。 必填项已用*标注

Time limit is exhausted. Please reload the CAPTCHA.