这篇文章记录在 Koa 应用里获取真实客户端 IP 的方法、X-Forwarded-For 等 HTTP 头部字段的含义,以及相关的 Nginx 配置。

ctx.request.ip

在 Koa 里获取客户端 IP 非常容易,简单来说,ctx.request.ip 就是客户端 IP。哪个 IP 请求过来的,哪个 IP 就是客户端 IP。

但是在生产环境服务器上,Koa 的前端往往还有一个反向代理服务器,比如 Nginx。这时候 ctx.request.ip 的值就是 Nginx 所在的 IP,往往就是本地地址 127.0.0.1。那么这时要怎么获取到客户端的真实 IP?

Koa 的文档里对 ctx.request.ip 的描述是这样的:

Request remote address. Supports X-Forwarded-For when app.proxy is true.

这句文档的后半句针对的就是上面提到的问题,也指明了方向。

下面是答案,做两件事情就可以了 :

  1. 在 Nginx 反向代理配置 proxy_pass 的部分添加这样一行。

    1
    
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  2. 在 Koa 应用里设置 app.proxytrue

这样,从 ctx.request.ip 拿到的就是客户端的真实 IP,而不是(反向)代理的 IP。

Nginx 通过设置 X-Forwarded-For 告诉 Koa 它为谁做代理。如果 Koa 中没有配置 app.proxy = true,Koa 会忽略 Nginx 在 HTTP 请求头部添加的 X-Forwarded-For 字段,所以要设置app.proxytrue

X-Forwarded-For

可以从 X-Forwarded-For 的名称里猜到,这个 HTTP 请求头部字段的含义是告诉 HTTP 的请求接收方「我在转发的谁的请求」,它并不只是包含最原始的客户端 IP,在维基百科的描述如下:

这一HTTP头一般格式如下:

X-Forwarded-For: client1, proxy1, proxy2

其中的值通过一个 逗号+空格 把多个IP地址区分开, 最左边(client1)是最原始客户端的IP地址, 代理服务器每成功收到一个请求,就把请求来源IP地址添加到右边。 在上面这个例子中,这个请求成功通过了三台代理服务器:proxy1, proxy2 及 proxy3。请求由client1发出,到达了proxy3(proxy3可能是请求的终点)。请求刚从client1中发出时,XFF是空的,请求被发往proxy1;通过proxy1的时候,client1被添加到XFF中,之后请求被发往proxy2;通过proxy2的时候,proxy1被添加到XFF中,之后请求被发往proxy3;通过proxy3时,proxy2被添加到XFF中,之后请求的的去向不明,如果proxy3不是请求终点,请求会被继续转发。

实际的生产环境中,极有可能并不是用户(客户端)-> 服务器 Nginx -> 服务器 Koa 这样简单的三个点,在用户和服务器之前,可能还有其他代理,比如 CDN,负载均衡,或许还有用户自己用的代理服务。通过上述方法,还是可以拿到最原始的真实客户端 IP,当然,这需要整个请求链条上各个点都遵照 X-Forwarded-For 头部的用法。

每个点把请求代理给下一个点时,都需要带上 X-Forwarded-For 头部字段,具体的做法是要把收到请求中的 X-Forwarded-For 拿过来,再给这个字段追加发起这个请求的节点 IP。

上面提到的 Nginx 配置 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 就是这么做的,在 Nginx 文档里有对 $proxy_add_x_forwarded_for 这个变量的说明:

$proxy_add_x_forwarded_for

the “X-Forwarded-For” client request header field with the $remote_addr variable appended to it, separated by a comma. If the “X-Forwarded-For” field is not present in the client request header, the $proxy_add_x_forwarded_for variable is equal to the $remote_addr variable.

$proxy_add_x_forwarded_for 是指请求过来的 HTTP 中 X-Forwarded-For 头部再追加上请求方自身的 IP($remote_addr)。

如果 Nginx 收到的请求中的 X-Forwarded-For 是空的,有可能这个 Nginx 是最靠近真实用户的节点,也有可能是前一节代理节点丢掉了 X-Forwarded-For 字段,这时 Nginx 转发请求时添加的 X-Forwarded-For 就是 $remote_addr 变量,即请求发起方的 IP。

ctx.request.ips

再回到 Koa。当 Koa 收到的请求中 X-Forwarded-For 包含多个 IP 时,也就是 X-Forwarded-For: client1, proxy1, proxy2 这样的形式,可以通过 ctx.request.ips 获取到这个 IP 列表,值为 [client1, proxy1, proxy2],ctx.request.ips[0]是真实客户端 IP(与ctx.request.ip是同一个 IP,即client1`),数组后面的各个 IP 是各级代理的 IP 地址。