## 背景

我们的 Web 服务,往往需要获取用户的真实 IP,譬如防刷、API 限流等等场景。

这似乎是一个显而易见的问题。

以 Node.js 来说,每一个 TCP 连接都有 remoteAddress 属性,通过它可以直接获取到请求的 IP 地址。而在 HTTP 请求中,我们可以通过 request.socket.remoteAddress 访问到这个属性。

可是事情真的有这么简单吗?

一般来说我们的应用服务都不会直接接收外部的请求,而会将服务部署在接入层之后,从而实现多台机器的负载均衡和服务的平滑发布,保证高可用。如阿里云 SLB 或 Nginx 反向代理。

此时,我们通过 remoteAddress 获取到的就是代理服务器的 IP 而不是用户的真实 IP。


## 常规解决方案

最常见的 Web 服务架构

既然这是一个通用的部署架构,那肯定有通用的解决方案来实现获取用户 IP 地址吧?

这个通用的解决方案就是 X-Forwarded-For 请求头。

简单的来说,就是所有的反向代理都实现一个统一的约定,在转发请求给下游服务之前,把请求代理的 IP 地址写入到 X-Forwarded-For 头中,形成了一个 IP 地址列:

X-Forwarded-For: client, proxy1, proxy2

这个方案虽然不是正式的 HTTP 协议,但已经成为了一个事实标准,基本上所有的反向代理服务都实现了这个功能,以确保下游的服务可以感知到经过的反向代理,并从中获取到用户的 IP 地址。

经查阅,发现有个 RFC 7239 规定了 Forwarded 请求头。(不过 Koa 没支持) Forwarded: for=127.0.0.1; proto=https, for=1.2.3.4; proto=https

看起来问题似乎完美解决。


## 真的解决了吗?

永远不要相信用户侧的输入。

可是总有一些“聪明”的用户,在遇到服务有做 IP 限制的时候,他们灵光一闪:假如我们在请求中直接加入 X-Forwarded-For 的请求头,是不是就可以伪装请求的 IP 了呢?

curl -H 'X-Forwarded-For: 1.2.3.4' http://iplimit.resource.com/api/resources

按照 X-Forwarded-For 的工作原理,收到它的第一个反向代理会就会认为原始的请求地址为 1.2.3.4 ,然后将真实的用户 IP 地址当做“第一个代理”加入到 X-Forwarded-For 中,最终在应用层的结果就变成了:

X-Forwarded-For: 1.2.3.4, client, proxy1, proxy2

如果我们的服务只按照之前的约定,将第一个 IP 地址当做用户的真实 IP,就被这些“聪明”的用户绕过了。甚至他们还有 Chrome 插件,直接把浏览器所有的请求都带上伪造的 IP 地址。

麻麻呀,这世界真危险,人与人最基础的信任呢?

## 解决方案

为了避免获取到伪造的用户 IP 地址,我们只能够更进一步,确定我们的部署架构上到底有多少个反向代理服务,从而在从 X-Forwarded-For 请求头中获取请求的真实 IP 时,过滤掉用户伪造的 IP 地址。

// 伪代码
// [ illegalIp, clientRealIp, proxyIp1, proxyIp2 ...]
const val = ctx.get('X-Forwarded-For');
let ips = val ? val.split(/s*,s*/) : [];
ips = ips.slice(-(maxProxyCount + 1));

很笨很挫的方式,不是么?但安全解决方案往往都是这样的 Dirty 和追求实用。

同时,作为企业级的 Node.js 框架,Egg.js 直接内置提供了对应的解决方案。

仅需简单的配置:

// config/config.default.js
config.proxy = true;
config.maxProxyCount = 1;

应用开发者即可无感知获取到正确的信息:

ctx.ip       // 获取用户的 IP 地址
ctx.host     // 获取用户请求的域名
ctx.protocol // 获取用户请求的协议

详细的方案,有兴趣的同学可以扩展阅读以下文档,其中也介绍了一些注意事项。

Egg 文档:前置代理模式​eggjs.org

有的同学可能会问了:是否可以直接在前面的接入层配置一个特殊的头,或者直接让它们覆盖掉 X-Forwarded-For 就可以了呢?

proxy_set_header X-Forwarded-For $remote_addr;

当然这也不失为一个解决方案,但是这个方案有它自己的问题,Egg.js 需要做兜底处理:

  • 并不是所有的统一接入层都受用户控制,例如阿里云 SLB 等商业负载均衡服务上,可能你就没法定制化这些需求了。
  • 框架更希望通过标准(事实标准)的解决方案来收敛功能,只要上层统一接入遵循标准,框架就可以直接接入,统一配置,而不需要用户根据不同的接入层来不同实现。
  • 两者并不冲突,框架兜底实现,default to good

## 关于本文

本方案源自于 语雀 的真实安全攻防案例,作者为蚂蚁金服的 ​ @死马 。

html获取当前ip地址_科普文:Node.js 安全攻防 - 如何伪造和获取用户真实 IP ?相关推荐

  1. ifconfig没有ip地址_虚拟机创建后该如何获取IP地址并访问互联网实用教程

    之前在做项目的时候主机IP地址.网关.DNS.子网掩码等都是公司或者对方直接给提供的,但是如果我们自己想搭建一台虚拟机或者一台集群的话,手头又没有IP地址,该肿么办呢? 白慌,这里介绍一个小技巧,亲测 ...

  2. 穿透代理获取用户真实IP地址

    文章目录 一.场景 二.方法 1.微信官方方法 (1)没有代理 (2)有代理 2.非官方方法 (1)代码 (2)说明 (3)补充 一.场景 在对接微信H5支付API时,有一关键步骤是获取到用户的真实I ...

  3. 北京dns服务器ip地址_什么是DNS? 域名系统,DNS服务器和IP地址概念介绍

    北京dns服务器ip地址 介绍 (Introduction) By the end of this article, you should have a better understanding of ...

  4. PHP获取用户真实IP地址

    PHP获取用户真实IP地址 <?phpfunction getRealIpAddr() { if (!empty($_SERVER['HTTP_CLIENT_IP'])) { $ip=$_SER ...

  5. 如何查电脑ip地址_摄像机地址不对连不上网?教你快速更改IP地址

    相信不少朋友有遇到过修改IP地址问题.有的用户在连接摄像头配网时会碰到连接不上网络的情况,经过排查网线是正常,路由器也没有做任何限制,后来发现是摄像机是静态IP地址且与路由器不是一个网段导致无法连接上 ...

  6. 电子邮件服务器的ip地址_可用的不同类型的IP地址

    有几种不同类型的IP地址可用.尽管无论哪种类型,IP地址的结构都是相同的,但它们的作用不同.例如,您有公用IP地址,专用IP地址,静态IP地址和动态IP地址. 动态和静态之间的区别是动态IP地址会更改 ...

  7. QQ空间迁移_【群晖NAS+FRP_并获取用户真实IP 支持群辉6.0和群辉7.0】

    群晖NAS+FRP 并获取用户真实IP 2020-11-12 13:57:54 事情的起因是这样的, 我的NAS没有公网IP,通过FRP端口映射到云主机对外提供访问,但是互联网不怀好意的人太多了,经常 ...

  8. java 不停的换ip地址_为什么电脑IP地址总是自动改变

    自己的IP会变化是因为自己或家庭使用的宽带不是专线,所以IP不是固定的.ADSL用户的IP地址都是通过DHCP动态获取得到的,意思是你每次拨号的时候,都会获得一个不同的IP地址,运营商这样做是为了提高 ...

  9. 笔记本java设置ip地址_如何设置苹果笔记本IP地址

    在相对于很少使用苹果笔记本的用户来说设置IP地址可能是个难题,下面我将介绍如何设置苹果笔记本IP地址. 1.单击桌面右上角的无线图标,无线网卡自动搜索环境中的无线网络,如下图所示. 2.点击" ...

  10. java获取用户真实IP地址

    /*** 获取用户真实IP地址,不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址.* 可是,如果通过了多级反向代理的话,X-Forwarded ...

最新文章

  1. 日本比中国快一个小时,泰国比中国慢一个小时
  2. 【数据结构与算法】之深入解析“将有序数组转换为二叉搜索树”的求解思路与算法示例
  3. 网络交换机的作用有哪些?
  4. 可以用在任何人身上:百战百胜人生10大成功秘诀
  5. 建立数字化、学习型人事平台,HR 与业务终于不再「隔空对话」
  6. Java 使用SAX解析XML文档
  7. c语言扔球第一次弹10米,c语言滚动字幕代码
  8. 基于同义词词林的文本相似度算法研究语料库
  9. 使用Go语言实现单词翻译功能/simpledict 命令行词典
  10. html 滚动条自动最底,让DIV的滚动条自动滚动到最底部 - 4种方法
  11. 抓包实现原理与反抓包
  12. 第一次学游泳技巧_新手学游泳第一次下水,学会如何将身体进入水中
  13. excel将一个工作表根据条件拆分成多个工作表,并将多个工作表怎么拆分成独立表格
  14. 超直线能否用于真实物理空间?
  15. html 把table固定住,html Table实现表头固定
  16. 软件测试学习笔记与思考(1)---软件测试基础
  17. 密码学的100个基本概念
  18. Windows系统下进行NIST测试
  19. python调用海康网络摄像头,实时显示监控内容
  20. Unix系统 - 进程管理

热门文章

  1. 190328每日一句 When you forgive, you release.
  2. 《图解算法》学习之算法复杂度、运行时间
  3. Unity2 学习 制作和动态加载预制体
  4. 2020-09-10 保证软件开发过程遵循ISO 26262标准的十个主要进阶步骤
  5. Atitit 管理plus 的概念,为什么要留长发与管理思想的结合 目录 1.1. 孝道的体现 身体发肤 受之发肤 不敢毁伤 出自 1 1.2. 著作介绍 1 1.3. 传统国学文化的复兴 中国
  6. Atitit. Class  元数据的反射操作 api apache  工具
  7. Atitit. 单点登录sso 的解决方案 总结
  8. 证券类私募主要需求及核心服务商
  9. Rust: 镜像设置要注意影响效果
  10. 不一样的摊余成本法债基—终篇