网站的流量越来越大后开始使用负载均衡来提高网站的并发数,负载均衡有很多选择,可以使用现成的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。
一、Roby on Rails获取用户ip的方法\
你拼错辣