Rails 动态 CDN+ 负载均衡网络条件下,Rails 获取用户真实 IP 的问题

crayon · 2016年11月16日 · 最后由 embbnux 回复于 2016年11月21日 · 5601 次阅读
本帖已被管理员设置为精华贴

背景

有人刷登录、注册、短信等接口,而且量还比较大,需要根据 IP,进行访问次数的限制。在没有 CDN 的情况下,获取用户 IP 还比较好办,有了动态 CDN,获取到的就基本都是 CDN 统一出口的 IP 了。这时候根据 IP 进行访问次数限制,就非常容易造成误伤。

Rails 获取用户 IP

大部分情况下,以下两行代码就够用了:

request.ip
request.remote_ip

request.ip,这个很多情况下可能会取到内网 IP,要看服务器机房的网络架构,但 request.remote_ip 肯定就能取到外网 IP 了。内网 IP 和外网 IP,大家一眼就能看出来,各取所需就行。

3 种由简入繁的网络请求情况

  • 无 CDN 无负载均衡

这种情况最好办,request.ip 就拿到外网 IP 了;

如果拿不到,用 request.remote_ip,我就用这个😅

  • 无 CDN 有负载均衡

这种情况,你就安心的用 request.remote_ip 吧😆

  • 有 CDN 有负载均衡

这种情况,request.remote_ip 获取到的是 CDN 的 IP,不是用户的😤

我的项目部署情况

  • Ruby 2.3.0
  • Rails 4.2.5
  • nginx 1.8.1
  • Phusion Passenger 5.0.26

用户真实 IP 在哪里?

我的 CDN 服务提供商,把用户真实 IP 放在下面这个请求头里:

Headers['Cdn-Src-Ip']

问一下自己的 CDN 服务提供商,或者用抓包工具抓一下,就可以确认了。请求头里没有的情况,我没遇到,暂时不知道如何解决,大家轻拍...😢

nginx 里的处理

首先,我们要确认一下,nginx 里到底有没有这个东西?打印出来看看😎

sudo vi /etc/nginx/nginx.conf

在 log_format 里,添加以下变量:

$http_cdn_src_ip

然后,保存配置,重启 nginx,去 access.log 里面看看是否打印出来了

tail -100f /var/log/nginx/access.log |more

nginx 里,是有用户的真实 IP 了;但是,Rails 里是没有 headers['Cdn-Src-Ip'] 的,想办法传过去!

Google 出来的,基本都是用这个方法:

proxy_set_header

看了好多国内外文章,经历过了 N 次配置和重启,都无效,真是想死的心都有,在线上生产环境,每次都要配好多台服务器......😹

后来,在 Passenger 旧版的官方文档里,发现了相关的东西,然后又找到了新版的文档,终于解决!

Passenger 5 设置额外 headers 的方法

passenger_set_header

nginx 配置文件里,参考设置如下:

server {
    listen 80;
    server_name  192.168.0.xxx;
    root /www/project-name/production/current/public;
    rails_env production;
    passenger_enabled on;
    passenger_set_header X-Real-IP $http_cdn_src_ip;
}

保存设置,重启 nginx,去 Rails 里看看去!

Rails 里的处理

因为我设置了:

passenger_set_header X-Real-IP $http_cdn_src_ip;

所以,我在 Rails 里是这么取的:

request.headers['X-Real-IP']

现在,总算是拿到用户的真实 IP 了!👏

这个问题,搞了挺久,分享出来,希望以后的人,少走弯路。有其他解决方法的,也欢迎分享出来!

huacnlee 将本帖设为了精华贴。 11月16日 09:47

核心就是要 passenger_set_headerproxy_set_header

如果是反向代理的话,proxy_set_header。如果是 passenger 的话 passenger_set_header 呗。

#2 楼 @huacnlee 是的,就是这一步卡了比较久

也可以设置 X-Forwarded-For,之前用 slb 也研究了一下:Rails 使用负载均衡后获取用户 ip

#5 楼 @embbnux 如果上游没有改过,通用情况 X-Forwarded-For 是能够解决问题的,但是上游另外设置了就得取上游设置的变量吧

类似的问题还有获取用户访问时的 scheme,之前折腾了好久才意识到在 app 层面如果不通过 referfer,其实是不知道用户访问的是 http 还是 https,也是需要在代理那边加一些特定的头部

主要是解决问题的思路

 根据源代码设置 X-Forwarded-For 的话,request.remote_ip 的值应该就是真实的 ip 地址。 没有环境,没法测试

x-非标准,还得看代理是否支持。

#6 楼 @limkurn

#9 楼 @roclv 对,比如楼主这里,改成这样:

passenger_set_header X-Forwarded-For $http_cdn_src_ip;

然后配置一下 在 rails 里配置下 custom_proxies,还是用 request.remote_ip 就可以拿到真实的 ip

需要 登录 后方可回复, 如果你还没有账号请 注册新账号