Laravel框架中 getClientIps() 原理和用法

起因是用 $request->ip() 来获取 ip 限流,突然遭到大面积误杀。排查 access.log 日志,几乎所有请求的 $remote_addr ,都为某几个固定 ip。咨询运维后发现是他悄悄给前端加了 cdn。那为何会产生这种问题呢?

remote_addr 和 http_x_forwarded_for

先来了解一下前置知识。

remote_addr 是真实的与 Web 服务建立连接的 ip。在不经过代理服务器的时候,能获取到用户真实IP,不可伪造

但多数请求都经过反向代理、CDN加速等服务,再到达 Web 服务。这时候与 Web 服务真实建立连接的就是代理服务器。相应地 remote_addr 的值也变成了代理服务器的 ip。那么经过代理服务器的服务,如何才能获取到用户真实IP呢?

X-Forwarded-For 则应运而生。它是一个 HTTP 扩展头部,用来记录一个请求途径服务器的ip,可以理解为一个 ip 链条。每途径一台代理服务器,它会把访问者的 ip,追加到 X-Forwarded-For 中。

但 X-Forwarded-For 是可以伪造的,客户端可能从最初发出的请求中 X-Forwarded-For 就携带者几个 ip。

格式如下:

X-Forwarded-For:client_ip, proxy1_ip, proxy2_ip

Laravel 获取 ip

Symfony 的 Request类中这样实现 getClientIps()

public function getClientIps()
{
    $ip = $this->server->get('REMOTE_ADDR');

    if (!$this->isFromTrustedProxy()) {
        return [$ip];
    }

    return $this->getTrustedValues(self::HEADER_X_FORWARDED_FOR, $ip) ?: [$ip];
}

首先,从 REMOTE_ADDR 中获取了与 Web 服务建立真实连接的客户端 ip。然后,判断此 ip 是否是受信任的代理,如果不是则直接返回此 ip。最后,调用 getTrustedValues() 来获取和处理 X-Forwarded-For。

(由于 Laravel 默认是受信任代理是空,所以导致了开篇提到的问题,如何设置信任代理见下文)

getTrustedValues() 主要有两个引入注目的操作:

  1. 如果 X-Forwarded-For 的 ip 链条中,也存在受信任的代理 ip,会被过滤。

  2. 将过滤后的 ip 链条反转返回;如果过滤后的 ip 链条 为空,则返回受信任的代理 ip。

getClientIps() 返回 ip 链接条,与 X-Forwarded-For 顺序相反,这时常会让开发者踩坑。那为什么会这么设计呢?

Symfony开发者在注释中是这么解释的:

返回的数组中,最受信任的 ip 排在第一位,最不受信任的 ip 排在最后一位。“真实”的客户端 ip 排在最后一位,但它也是最不受信任的。受信任的代理 ip 已被从中移除。

看完你会恍然大悟。

如何食用

假如,一个请求如下:

client->proxy1->proxy2->proxy3->web service

proxy1-3 是我们的代理服务器

那么,它的 X-Forwarded-For:client_ip, p1_ip, p2_ip

由于,proxy1-3 是我们的代理服务器是可信的。

我们将 p1_ip, p2_ip, p3_ip 加入受信任代理。

namespace App\Http\Middleware;

……

class TrustProxies extends Middleware

{

    protected $proxies = [

        p1_ip, p2_ip, p3_ip

    ];

……

那么,getClientIps() 会返回

[
    client_ip
]

由于,client_ip 是由 proxy1 代理服务器追加到 X-Forwarded-For,所以是可信的,可作为用户的真实IP。

如果,客户端请求前 X-Forwarded-For 就已有一个 ip(client0_ip)

getClientIps() 会返回

// 信任度从高到底
[
    client_ip,
    client0_ip
]

client0 有可能是用户真实的ip,也有可能是自己伪造的 X-Forwarded-For,并不能信任它。

参考文章

www.cnblogs.com/lcawen/articles/92...

博客:关于 Laravel 使用了 CDN 获取真实 IP 记录

PS:写完后,发现一篇写的很棒的文章 segmentfault.com/a/119000002208025...

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!