应用获取客户端真实IP


!! 大家好,我是乔克,一个爱折腾的运维工程,一个睡觉都被自己丑醒的云原生爱好者。

作者:乔克

公众号:运维开发故事

博客:www.jokerbai.com

背景信息

因为产品需要,要在应用端获取到真实的客户端IP,访问链路如下:

由于应用前面经过了多次代理,所以默认情况下是获取不到真实IP的。

基本概念

在实现之前,先简单了解一下常用来获取IP的几个header。

  • remote_addr

  • X-Forwarded-For

  • X-Real-IP

(1)remote_addr

remote_addr代表客户端IP,但是它的值不是由客户端提供的,而是服务端根据客户端IP指定的。当你访问某个应用时,当中间没有经过任何代理,那么应用获取到的remote_addr就是你的主机IP。如果中间经过了代理转发,正常情况下,应用获取到的remote_addr就是代理的IP,除非在代理服务器上手动将remote_addr的地址设置成你的主机IP。

(2)X-Forwarded-For

X-Forwarded-ForHTTP扩展头,简称XFF

XFF 的内容由「英文逗号 + 空格」隔开的多个部分组成,最开始的是离服务端最远的设备 IP,然后是每一级代理设备的 IP,其格式为:X-Forwarded-For: client,proxy1,proxy2

!! PS: X-Forwarded-For的格式可以被伪造。

如果一个应用的前面有三个代理,分别是 Proxy1Proxy2Proxy3,它们的IP地址分别是IP1IP2IP3,用户的真实IP为IP0,那么按照XFF标准,应用收到的XFF信息应该如下:X-Forwarded-For: IP0,IP1,IP2。这里没有IP3,是因为Proxy3是转发Proxy2的代理,在转发过程中会将Proxy2的IP地址追加到XFF中,而自己的IP地址会被放入remote_addr中。

!! PS:通过一个代理才会把上一个节点的IP加到XFF中。

(3)X-Real-IP

X-Real-IP是一个自定义的头部字段,通常被HTTP代理用来表示与它产生TCP连接的设备IP,和XFF不一样的是,它不是列表,无法将记录追加到X-Real-IP的结尾,而是直接替换。

理想情况下,我们需要达到以下效果:

也就是应用获取到的X-Real-IP就是客户端的真实IP,这就要求除了第一层代理之外,后面的代理不需要再去设置X-Real-IP,只需要做转发即可,这样应用就能拿到真实的客户端访问IP了。

具体实现

由于现实情况,我们在Haproxy上会做很多的规则配置,所以第一层SLB上是单纯的TCP代理,因此在SLB上不需要做太多额外配置,客户端IP会直接透传过去。

当请求到达Haproxy之后,需要将客户端IP加到XFF中,并且设置X-Real-IP为客户端IP,具体配置如下:

perl 复制代码
defaults
                mode                    http
                log                     global
                option                  httplog
                option                  dontlognull
                option http-server-close
                log 127.0.0.1 local3
                option forwardfor       except 127.0.0.0/8
                option                  redispatch
                retries                 3
                timeout http-request    10s
                timeout queue           1m
                timeout connect         10s
                timeout client          5m
                timeout server          5m
                timeout http-keep-alive 10s
                timeout check           10s
                unique-id-format %{+X}o\ %ci%cp%fi%fp%Ts%rt%pid

frontend https_link_ha
        bind *:443 ssl crt /usr/local/etc/haproxy/cert/crt/ ca-file /usr/local/etc/haproxy/cert/ca/ca.pem verify optional
        #log 127.0.0.1 local3
        mode http
        log-format "%ID %ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r"
        option accept-invalid-http-request

        http-request set-header x-request-id %[unique-id]
        http-request set-header x-request-time %[date()]
        http-request set-header X-Real-IP %[src]

        default_backend pre

backend pre 
    server 1 10.74.136.13:8080 check inter 1500 rise 3 fall 3 weight 3

其中主要是两个配置:

  • option forwardfor except 127.0.0.0/8在由Haproxy发往后端的请求中加上XFF首部,其值是前个客户端的IP。

  • http-request set-header X-Real-IP %[src]X-Real-IP中设置客户端IP。

现在请求就到达Ingress了。

Ingress也是一层代理,前面的客户端IP已经放到XFF中,但是默认情况下,Ingress没有开启XFF

Ingress上要使用XFF,需要使用到以下三个参数:

  • use-forwarded-headers:是否开启XFF头传递,默认是false

  • forwarded-for-headerXFF的真实header名,默认是X-Forwarded-For

  • compute-full-forwarded-for:列出客户端访问所经过的代理IP,默认情况下,XFF是从remote_addr中获取的值。

所以,我们只需要在Nginx IngressConfigMap里增加以下两个配置即可:

perl 复制代码
use-forwarded-headers: 'true'
compute-full-forwarded-for: 'true'

配置完成后,Nginx Ingress会自动重载服务,不需要单独重启。

然后在应用的日志里就能获取到客户端的真实IP了。

当然,并不是所有的场景都能通过XFF获取到用户的真实IP,比如当SLB前面还有CDN的情况下,获取的可能就是CDN的来源IP了。

最后,求关注。如果你还想看更多优质原创文章,欢迎关注我们的公众号「运维开发故事」。

相关推荐
二闹3 分钟前
三个注解,到底该用哪一个?别再傻傻分不清了!
后端
用户490558160812515 分钟前
当控制面更新一条 ACL 规则时,如何更新给数据面
后端
林太白16 分钟前
Nuxt.js搭建一个官网如何简单
前端·javascript·后端
码事漫谈18 分钟前
VS Code 终端完全指南
后端
该用户已不存在43 分钟前
OpenJDK、Temurin、GraalVM...到底该装哪个?
java·后端
怀刃1 小时前
内存监控对应解决方案
后端
码事漫谈1 小时前
VS Code Copilot 内联聊天与提示词技巧指南
后端
Moonbit1 小时前
MoonBit Perals Vol.06: MoonBit 与 LLVM 共舞 (上):编译前端实现
后端·算法·编程语言
Moonbit1 小时前
MoonBit Perals Vol.06: MoonBit 与 LLVM 共舞(下):llvm IR 代码生成
后端·程序员·代码规范
Moonbit2 小时前
MoonBit Pearls Vol.05: 函数式里的依赖注入:Reader Monad
后端·rust·编程语言