获取请求来源 IP 地址

8,050 阅读3分钟

IP 追溯主要用于获取请求的真实 IP ,由于现有服务是基于 Nginx 实现负载均衡的,因此获取请求真实 IP 存在一定难度。在 Matrix 现有实现中,IP 追溯由 getIp 函数完成,其具体代码如下。

/**
 * 获得请求发送方的 ip
 * @param   {Context}  ctx
 * @return  {string}
 */
export function getIp(ctx) {
  const xRealIp = ctx.get('X-Real-Ip');
  const { ip } = ctx;
  const { remoteAddress } = ctx.req.connection;
  return xRealIp || ip || remoteAddress;
}

在对代码进行更为详细的讨论之前,我们需要分析几种常用获取请求 IP 来源的方式:

  • req.socket.remoteAddress
  • X-Forwarded-For
  • X-Real-IP

req.socket.remoteAddress

在 Node.js 官方文档 net_socket_remoteaddress 中,我们得知可通过 req.socket.remtoeAddress 获取 Socket 连接的源 IP 地址信息。

socket.remoteAddress

Added in: v0.5.10

  • String

The string representation of the remote IP address. For example, '74.125.127.100' or '2001:4860:a005::68'. Value may be undefined if the socket is destroyed (for example, if the client disconnected).

根据官方文档 http_request_socket 的描述,req.connectionreq.socket 是等价的,我们也可以通过 req.connection.remoteAddress 获取 Socket 连接的源 IP 信息。这种获取请求 IP 来源的方式,适用于客户端直连服务端的场景。

但由于现有 Matrix 服务使用了 Nginx 作为服务集群的负载均衡,故通过 req.socket.connection 获取得到的是 Nginx 的 IP 地址,并不是请求的真实 IP 地址。

X-Forwarded-For

根据 RFC 7239 规范,HTTP 代理(如 Nginx、Apache 等)会改写 HTTP 请求头部,添加 X-Forwarded-For 字段,用于追踪请求的来源,该字段的格式如下:

X-Forwarded-For: client, proxy1, proxy2

下面对 X-Forwarded-For 字段的处理过程,进行详细阐述:

  • 客户端(如浏览器)在发送 HTTP 请求时,默认是不带 X-Forwarded-For 字段。
  • 当请求到达第一个 HTTP 代理服务器时,代理服务器根据 RFC 7239 规范,为请求头部添加 X-Forwared-For 字段,并且将值设置为客户端的 IP 地址
  • 若后面存在多个 HTTP 代理(即请求在后续会依次被多个 HTTP 代理处理),则每个代理都会在 X-Forwarded-For 字段值中追加上一代理的 IP 地址
  • 在业务服务器中,我们可通过 X-Forwarded-For 值的最左边的 IP 地址获取客户端的 IP 地址。

但遗憾的是,X-Forwarded-For可伪造的:如果客户端在发起 HTTP 请求时,设置了伪造的 X-Forwarded-For 字段,由于后续的每层代理只会追加此字段而不会覆盖,故业务服务器最终获取的客户端 IP 地址可能是客户端伪造的地址。

通常,我们需要进行额外的配置,才能使得 Nginx 支持 X-Forwarded-For

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

X-Real-IP

HTTP 代理服务器可在请求头部中设置 X-Real-IP 请求源信息,但这并不是 RFC 规范中的内容

X-Real-IP客户端不可伪造的,但仅能描述最近一个代理的真实 IP ,如果有多层代理,其仍旧不可作为客户端请求的真实 IP 。在现实生活中,多层代理其实是比较少见的,常见的是单层代理,因此在通常情况下,使用 X-Real-IP 是足以完成任务的,且相对于 X-Forwarded-For 有更好的安全性。

我们可通过以下设置,使得 Nginx 支持 X-Real-IP 字段的自动设置。

proxy_set_header X-Real-IP $remote_addr;

几种方式的对比

以下是我对几种获取客户端真实 IP 的方式的总结,分析了各方式的局限和应用场景。

req.socket.remoteAddress X-Forwarded-For X-Real-IP
是否可伪造
有效性 仅在客户端直连服务端时 仅在未伪造时 仅在客户端未经过多级代理时

参考资料