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

crayon · November 16, 2016 · Last by embbnux replied at November 21, 2016 · 5523 hits
Topic has been selected as the excellent topic by the admin.

背景

有人刷登录、注册、短信等接口,而且量还比较大,需要根据 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 mark as excellent topic. 16 Nov 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

You need to Sign in before reply, if you don't have an account, please Sign up first.