【雨夜】request 获取的就是真实的ip么?

最近有一个项目(调用第三方接口)需要添加验证白名单功能,周五和老大在探讨的时候,我们说到了获取ip ,我当时是这么说的

我:

"我让header 传递ip过来 "

老大:

"直接request 直接获取ip不就行了么 "

老大意思应该是直接调用如下这类代码搞定

ini 复制代码
package com.example.winterholity.util;

import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;

public class IpUtil {
    public static String getIpAddr(HttpServletRequest request) {
        String ipAddress = null;
        try {
            ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
                if (ipAddress.equals("127.0.0.1")) {
                    // 根据网卡取本机配置的IP
                    InetAddress inet = null;
                    try {
                        inet = InetAddress.getLocalHost();
                    } catch (UnknownHostException e) {
                        e.printStackTrace();
                    }
                    ipAddress = inet.getHostAddress();
                }
            }
            // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
            if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
                // = 15
                if (ipAddress.indexOf(",") > 0) {
                    ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                }
            }
        } catch (Exception e) {
            ipAddress="";
        }
        // ipAddress = this.getRequest().getRemoteAddr();
        return ipAddress;
    }
}

但是我感觉不一定

问题

这个方法就一定获取的是真实的IP么?

先搭建一个环境再说

简单测试 ip获取成功

但是在互联网 请求 nginx 的接口,业务系统获取的是 nginx的ip,这就不对了,应该是返回互联网的IP 才对

如果request.getRemoteAddr() 就可以获取真实ip,那就不会有x-forwarded-for 等属性的事了

request.getRemoteAddr() 和 request.getRemoteHost() 区别

csharp 复制代码
System.out.println("request.getRemoteAddr(): " + request.getRemoteAddr());   
System.out.println("request.getRemoteHost(): " + request.getRemoteHost());

前一个是获得客户端的ip地址

后一个是获得客户端的主机名

request.getRemoteAddr() 里面的ip 是怎么获取的

kotlin 复制代码
public String getRemoteAddr() {  
    if (this.remoteAddr == null) {  
        this.coyoteRequest.action(ActionCode.REQ_HOST_ADDR_ATTRIBUTE, this.coyoteRequest);  
        this.remoteAddr = this.coyoteRequest.remoteAddr().toString();  
    }  
  
return this.remoteAddr;  
}

不一定获取的是真实的IP,可能获取的是 nginx等中间件的ip,甚至伪造的也是可能的

因为负载均衡 可能丢弃真实ip,有什么办法进行

  1. 应用层方法
  2. 传输层方法
  3. 网络层方法

应用层

在这一层,Web 协议的制定者们想到了一个巧妙的办法:既然 HTTP 协议比较灵活,那就可以设计一个新的 header,用来传递真实源 IP,它就是 X-Forwarded-For。这个标准最初是 Squid 的开发工程师提出的,很快受到了业界的支持,各种 web 服务器都早已支持了这个 header。

他是通过X-Forwarded-For 直接传递进去

不过,X-Forwarded-For 这个标准,虽然用一种相对低的成本解决了"服务器不能获取真实源 IP"的问题,但它本身还是有一些不足的

源 IP 信息的伪造问题

这也是它最大的问题,因为这个头部本身没有任何安全保障机制,攻击者完全可以任意构造 X-Forwarded-For 信息来欺骗服务端。比如,如果攻击者知道服务端对某个 IP 段来的请求进行特殊处理(比如会提供更大力度的优惠券),那么攻击者就可以在发送请求时候,构造一个 X-Forwarded-For 头部,它的值就是这个段内的某个 IP。当服务端收到请求时,认为 X-Forwarded-For 里排在最左边的 IP 是真实 IP,而事实上这个是伪造出来的,所以可想而知,这个请求就可以获取它原本不应该得到的特权了。

重复的 X-Forwarded-For 头部

HTTP 协议本身并不严格要求 header 是唯一的,所以有些情况下,HTTP 请求可能会携带两个或者更多的 X-Forwarded-For 头部。

造成这个现象的原因是,某些代理或者 LB 并不是严格按照协议规定的,把 IP 附加到已有的 X-Forwarded-For 头部,而是自己另起一个 X-Forwarded-For 头部,那么这样就导致了重复的 X-Forwarded-For。

对于服务端来说,在收到这种请求的时候,可能会导致信息识别上的错乱。比如某些服务端的逻辑是读取第一个 X-Forwarded-For,而另外一些服务端程序可能是读取最后一个,并无定法

不能解决 HTTP 和邮件协议以外的真实源 IP 获取的需求

X-Forwarded-For 解决了 HTTP 的透传真实源 IP 的需求,但是事实上,很多应用并不是基于 HTTP 协议工作的,比如数据库、FTP、syslog 等等,这些场景也需要"获取真实源 IP"这个功能。但是前面说的 X-Forwarded-For,只能为 HTTP/ 邮件协议所用,那其他这么多协议和应用难道就成了没妈的孩子,永远不能获取到真实源 IP 了吗?

这时候,传输层的方法就上场了。

传输层方法

TOA 和 TCP Options

TOA 全称是 TCP Option Address,它是利用 TCP Options 的字段来承载真实源 IP 信息,这个是目前比较常见的第四层方案。不过,这并非是 TCP 标准所支持的,所以需要通信双方都进行改造。也就是:

对于发送方来说,需要有能力把真实源 IP 插入到 TCP Options 里面。

对于接收方来说,需要有能力把 TCP Options 里面的 IP 地址读取出来。

可见,TCP Options 是可变长的,最长为 40 字节(第一列的偏移量 20 到 60 字节之差)。每个 Option 项由三部分组成:

扩展 SYN 报文的 TCP Options,让它携带真实源 IP 信息。这个需要对中间的 LB 和后端服务器都进行小幅的配置改造。

Proxy Protocol

这个方案是 HAProxy(另外一个广泛应用的反向代理软件)工程师提出的。它的实现原理是这样的:

  1. 客户端在 TCP 握手完成之后,在应用层数据发送之前,插入一个包,这个包的 payload 就是真实源 IP。也就是说,在三次握手后,第四个包不是应用层请求,而是一个包含了真实源 IP 信息的 TCP 包,这样应用层请求会延后一个包,从第五个包开始。
  2. 服务端也需要支持 Proxy Protocol,以此来识别三次握手后的这个额外的数据包,提取出真实源 IP。

这是一个逐步被各种反向代理和 HTTP Server 软件接纳的方案,可以在不改动代码或者内核配置的情况下,只修改反向代理和 HTTP Server 软件的配置就能做到。

为什么会有 x-forwarded-for

我们第三方调用不是http 就是webservice,其他的很少(其中http是大多数),针对这种情况,我们都会在nginx配置如下

如果发送请求设置X-Forwarded-For=xxx,然后nginx 设置

ini 复制代码
location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header Host $http_host;
    proxy_set_header X-NginX-Proxy true;
 
    proxy_pass http://127.0.0.1:9009/;
    proxy_redirect off;
}

后台 获取的ip为 xxx的IP,实际获取的ip为xxx

上面这个配置有问题么?

bash 复制代码
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;

这样配置之后,安全性确实提高了,但是也导致请求到达 Nginx 之前的所有代理信息都被抹掉,无法为真正使用代理的用户提供更好的服务。还是应该弄明白这中间的原理,具体场景具体分析。

应该用

ini 复制代码
location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-NginX-Proxy true;
 
    proxy_pass http://127.0.0.1:9009/;
    proxy_redirect off;
}

思考题

我有一个场景也是用了本文章的内容,您看看怎么实现

背景

我们对接了很多的第三方平台接口,需要部署我们的agent,但是第三方接口经常出问题,但是用户看到有问题了,肯定找我们,或者对我们的影响不好

第三方接口都是http请求,但是其中 怎么转发的不清楚

然后我们就要找原因,找到是2,3 中间的问题,但是这两方都不认为是自己的问题,让我们找证据,他们的网络环境,对我们来说是黑盒,这种情况,你怎么定位问题出现在哪

需求

  1. agent要验证白名单ip
  2. 需要验证是哪个节点 出现了问题,方便找对应的人
  3. 需要满足以后第三方接口(未来是未知的) 各种情况

分析

其实如果LB 都是我们自己维护 ,当然怎么搞都可以实现,但是往往调用第三方接口,LB 不一定怎么实现的,有的 直接header 给你过滤掉都是有可能的,这就给我们增加了难度

相关推荐
一个 00 后的码农21 分钟前
25旅游管理研究生复试面试问题汇总 旅游管理专业知识问题很全! 旅游管理复试全流程攻略 旅游管理考研复试真题汇总
考研·面试·面试问题·考研复试·旅游管理·复试调剂·面试真题
坚定信念,勇往无前1 小时前
springboot单机支持1w并发,需要做哪些优化
java·spring boot·后端
老友@1 小时前
OnlyOffice:前端编辑器与后端API实现高效办公
前端·后端·websocket·编辑器·onlyoffice
Twilight-pending2 小时前
DeepSeek 新注意力架构NSA
架构
风月歌2 小时前
基于springboot校园健康系统的设计与实现(源码+文档)
java·spring boot·后端·mysql·毕业设计·mybatis·源码
m0_748239473 小时前
Spring Boot框架知识总结(超详细)
java·spring boot·后端
m0_748236113 小时前
Spring Boot 实战:轻松实现文件上传与下载功能
linux·spring boot·后端
m0_748245923 小时前
Spring Boot(七):Swagger 接口文档
java·spring boot·后端
青灯文案14 小时前
如何在 SpringBoot 项目使用 Redis 的 Pipeline 功能
spring boot·redis·后端
m0_748249544 小时前
SpringBoot教程(三十二) SpringBoot集成Skywalking链路跟踪
spring boot·后端·skywalking