跳转到内容

IP 地址

IP 地址在 HTTP 中扮演基础角色,它用于访问控制、审计、基于地理位置的分析等。 Echo 暴露 Context#RealIP() 来获取它。

获取_真实_客户端 IP 并不简单,尤其是当 L7 代理位于应用前方时。在这种情况下, 真实 IP 必须由代理通过 HTTP 转发给你的应用,但你不能无条件信任 HTTP header, 否则就可能被欺骗。这是一个安全风险。

要可靠且安全地获取 IP,你的应用必须了解整个基础设施。在 Echo 中,你通过 Echo#IPExtractor 配置这一点。

从两个问题开始,找到正确方法:

  1. 你的应用前面是否有任何 HTTP(L7)代理?这包括云负载均衡器(如 AWS ALB 或 GCP HTTP LB) 和开源代理(如 Nginx、Envoy 或 Istio ingress gateway)。
  2. 如果有,这些代理使用哪个 HTTP header 将客户端 IP 传递给应用?

如果没有代理(应用直接面向互联网),唯一可以信任的地址来自网络层。每个 HTTP header 都不可信, 因为客户端可以完全控制它们。

使用 echo.ExtractIPDirect()

e.IPExtractor = echo.ExtractIPDirect()

情况 2:使用 X-Forwarded-For header 的代理

Section titled “情况 2:使用 X-Forwarded-For header 的代理”

X-Forwarded-For (XFF) 是转发客户端 IP 最常见的 header。每经过一跳,代理都会把请求 IP 追加到 header 末尾。

┌──────────┐ ┌──────────┐ ┌──────────┐
───────────>│ Proxy 1 │───────────>│ Proxy 2 │───────────>│ Your app │
│ (IP: b) │ │ (IP: c) │ │ │
└──────────┘ └──────────┘ └──────────┘
Case 1.
XFF: "" "a" "a, b"
~~~~~~
Case 2.
XFF: "x" "x, a" "x, a, b"
~~~~~~~~~
↑ What your app will see

在这种情况下,应从右侧开始取得第一个不可信的 IP 读数。永远不要取最左边的第一个, 因为客户端控制它。这里“可信”表示你确定该 IP 属于你的基础设施。在上面的例子中, 如果 bc 可信,则两种情况下客户端 IP 都是 a,绝不是 x

使用 ExtractIPFromXFFHeader(...TrustOption)

e.IPExtractor = echo.ExtractIPFromXFFHeader()

默认情况下,它信任内部 IP 地址,包括 loopback、link-local unicast、private-use, 以及来自 RFC 6890RFC 4291RFC 4193 的 unique local 地址。 可用 TrustOption 控制这一点:

e.IPExtractor = echo.ExtractIPFromXFFHeader(
echo.TrustLoopback(false), // e.g. IPv4 starting with 127.
echo.TrustLinkLocal(false), // e.g. IPv4 starting with 169.254.
echo.TrustPrivateNet(false), // e.g. IPv4 starting with 10. or 192.168.
echo.TrustIPRange(lbIPRange),
)

情况 3:使用 X-Real-IP header 的代理

Section titled “情况 3:使用 X-Real-IP header 的代理”

X-Real-IP 是另一个用于转发客户端 IP 的 header,但与 XFF 不同,它只携带单个地址。

如果你的代理设置了这个 header,请使用 ExtractIPFromRealIPHeader(...TrustOption)

e.IPExtractor = echo.ExtractIPFromRealIPHeader()

与 XFF 一样,它默认信任内部 IP 地址,并接受相同的 TrustOption

默认情况下,Echo 会同时考虑第一个 XFF header、X-Real-IP header 和网络层 IP。

正如本文应当说明的那样,这并不是好的选择。它仅为了向后兼容而保留为默认值。