程序在Nginx/k8s下如何获取客户端真实IP,带工具类

4 篇文章 0 订阅

目录

Nginx获取客户端信息

直接获取信息存在哪些问题?

如何解决这些问题?

我们整体上需要从两个方面来解决这些问题:

具体实践

配置nginx

通过Java方法获取客户端信息

Tomcat

k8s ingress获取真实IP地址配置 


Nginx获取客户端信息

Nginx反向代理后,Servlet应用通过request.getRemoteAddr()取到的IP是Nginx的IP地址,并非客户端真实IP,通过request.getRequestURL()获取的域名、协议、端口都是Nginx访问Web应用时的域名、协议、端口,而非客户端浏览器地址栏上的真实域名、协议、端口。

直接获取信息存在哪些问题?

例如在某一台IP为192.168.101.1的服务器上,Tomcat端口号为8090,Nginx端口号80,Nginx反向代理8090端口:

server {
    listen 80;
    location / {
        proxy_pass http://127.0.0.1:8090; # 反向代理应用服务器HTTP地址
    }
}

在另一台ip为192.168.10.11机器上用浏览器打开http://192.168.101.1/api/getIp访问自己写的一个spring boot应用,获取客户端IP和URL:

log.info("RemoteAddr: {}" , request.getRemoteAddr());
log.info("URL: {}" ,request.getRequestURL().toString());

打印的结果信息如下:

RemoteAddr: 127.0.0.1
URL: http://127.0.0.1:8090/api/getIp

可以发现,当前程序获取到的客户端IP是Nginx的IP而非192.168.10.11浏览器所在机器的IP,获取到的URL是Nginx proxy_pass配置的URL组成的地址,而非浏览器地址栏上的真实地址。如果将Nginx用作https服务器反向代理后端的http服务,那么request.getRequestURL()获取的URL是http前缀的而非https前缀,无法获取到浏览器地址栏的真实协议。如果此时将request.getRequestURL()获取得到的URL用作拼接Redirect地址,就会出现跳转到错误的地址,这也是Nginx反向代理时经常出现的一个问题。

如何解决这些问题?

既然直接使用Nginx获取客户端信息存在问题,那我们该如何解决这个问题呢?

我们整体上需要从两个方面来解决这些问题:

(1)由于Nginx是代理服务器,所有客户端请求都从Nginx转发到Tomcat,如果Nginx不把客户端真实IP、域名、协议、端口告诉Tomcat,那么Tomcat应用永远不会知道这些信息,所以需要Nginx配置一些HTTP Header来将这些信息告诉被代理的Tomcat;

(2)Tomcat这一端,不能再获取直接和它连接的客户端(也就是Nginx)的信息,而是要从Nginx传递过来的HTTP Header中获取客户端信息。

具体实践

配置nginx

首先,我们需要在Nginx的配置文件nginx.conf中添加如下配置。

proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

各参数的含义如下所示。

  • Host包含客户端真实的域名和端口号;
  • X-Forwarded-Proto表示客户端真实的协议(http还是https);
  • X-Real-IP表示客户端真实的IP;
  • X-Forwarded-For这个Header和X-Real-IP类似,但它在多层代理时会包含真实客户端及中间每个代理服务器的IP。

此时,再试一下request.getRemoteAddr()request.getRequestURL()的输出结果:

RemoteAddr: 127.0.0.1
URL: http://192.168.101.1/api/getIp

可以发现URL好像已经没问题了,但是IP还是本地的IP而非真实客户端IP。但是如果是用Nginx作为https服务器反向代理到http服务器,会发现浏览器地址栏是https前缀但是request.getRequestURL()获取到的URL还是http前缀,也就是仅仅配置Nginx还不能彻底解决问题。

通过Java方法获取客户端信息

以下示例是以java为例。适用于Nginx代理和k8s部署

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

/**
 * 获取IP方法
 * 防止反向代理反向代理软件获取不到真实iPx信息
 *@author 重庆阿汤哥
 */
public class IpUtils {
   public static String getIpAddr(HttpServletRequest request) {
      if (request == null) {
         return "unknown";
      }
      String ip = request.getHeader("x-forwarded-for");
      if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
         ip = request.getHeader("Proxy-Client-IP");
      }
      if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
         ip = request.getHeader("X-Forwarded-For");
      }
      if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
         ip = request.getHeader("WL-Proxy-Client-IP");
      }
      if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
         ip = request.getHeader("X-Real-IP");
      }

      if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
         ip = request.getRemoteAddr();
      }
      ip = EscapeUtil.clean(ip);// 清除Xss特殊字符
      return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
   }

   private static boolean internalIp(byte[] addr) {
      if (Objects.isNull(addr) || addr.length < 2) {
         return true;
      }
      final byte b0 = addr[0];
      final byte b1 = addr[1];
      // 10.x.x.x/8
      final byte SECTION_1 = 0x0A;
      // 172.16.x.x/12
      final byte SECTION_2 = (byte) 0xAC;
      final byte SECTION_3 = (byte) 0x10;
      final byte SECTION_4 = (byte) 0x1F;
      // 192.168.x.x/16
      final byte SECTION_5 = (byte) 0xC0;
      final byte SECTION_6 = (byte) 0xA8;
      switch (b0) {
         case SECTION_1:
            return true;
         case SECTION_2:
            if (b1 >= SECTION_3 && b1 <= SECTION_4) {
               return true;
            }
         case SECTION_5:
            switch (b1) {
               case SECTION_6:
                  return true;
            }
         default:
            return false;
      }
   }

   /**
    * 将IPv4地址转换成字节
    *
    * @param text IPv4地址
    * @return byte 字节
    */
   public static byte[] textToNumericFormatV4(String text) {
      if (text.length() == 0) {
         return null;
      }

      byte[] bytes = new byte[4];
      String[] elements = text.split("\\.", -1);
      try {
         long l;
         int i;
         switch (elements.length) {
            case 1:
               l = Long.parseLong(elements[0]);
               if ((l < 0L) || (l > 4294967295L))
                  return null;
               bytes[0] = (byte) (int) (l >> 24 & 0xFF);
               bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);
               bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
               bytes[3] = (byte) (int) (l & 0xFF);
               break;
            case 2:
               l = Integer.parseInt(elements[0]);
               if ((l < 0L) || (l > 255L))
                  return null;
               bytes[0] = (byte) (int) (l & 0xFF);
               l = Integer.parseInt(elements[1]);
               if ((l < 0L) || (l > 16777215L))
                  return null;
               bytes[1] = (byte) (int) (l >> 16 & 0xFF);
               bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
               bytes[3] = (byte) (int) (l & 0xFF);
               break;
            case 3:
               for (i = 0; i < 2; ++i) {
                  l = Integer.parseInt(elements[i]);
                  if ((l < 0L) || (l > 255L))
                     return null;
                  bytes[i] = (byte) (int) (l & 0xFF);
               }
               l = Integer.parseInt(elements[2]);
               if ((l < 0L) || (l > 65535L))
                  return null;
               bytes[2] = (byte) (int) (l >> 8 & 0xFF);
               bytes[3] = (byte) (int) (l & 0xFF);
               break;
            case 4:
               for (i = 0; i < 4; ++i) {
                  l = Integer.parseInt(elements[i]);
                  if ((l < 0L) || (l > 255L))
                     return null;
                  bytes[i] = (byte) (int) (l & 0xFF);
               }
               break;
            default:
               return null;
         }
      } catch (NumberFormatException e) {
         return null;
      }
      return bytes;
   }

这种方式虽然能够获取客户端的IP地址,但涉及到代码侵入,既然Servlet API提供了request.getRemoteAddr()方法获取客户端IP,那么通过改Tomcat服务器的配置岂不是更好的获取客户端信息。

Tomcat

如果使用Tomcat作为应用服务器,可以通过配置Tomcat的server.xml文件,在Host元素内最后加入:

<Valve className="org.apache.catalina.valves.RemoteIpValve" />

k8s ingress获取真实IP地址配置 

 正常公司开发者基本无权限配置,需要联系自己公司运维,在racher中找到ingress-nginx-controller,在找到nginx-configuration

 

 添加内容:

compute-full-forwarded-for: "true"
forwarded-for-header: "X-Forwarded-For"
use-forwarded-headers: "true"

保存后重启pod。随后ingress的添加真实的IP行为会与RFC一样都依次添加到X-Forwarded-For中了

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值