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