# real-ip **Repository Path**: llhl001/real-ip ## Basic Information - **Project Name**: real-ip - **Description**: 获取客户端真实IP - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2023-11-16 - **Last Updated**: 2023-11-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 如何获取客户端真实IP nginx配置 首先,一个请求肯定是可以分为请求头和请求体的,而我们客户端的IP地址信息一般都是存储在请求头里的。如果你的服务器有用Nginx做负载均衡的话,你需要在你的location里面配置X-Real-IP和X-Forwarded-For请求头: ```bash location ^~ /your-service/ {     proxy_set_header X-Real-IP $remote_addr;     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;     proxy_pass http://localhost:8080/your-service/; } ``` X-Real-IP 在《实战nginx》中,有这么一句话:经过反向代理后,由于在客户端和web服务器之间增加了中间层,因此web服务器无法直接拿到客户端的ip,通过\$remote_addr变量拿到的将是反向代理服务器的ip地址。 这句话的意思是说,当你使用了nginx反向服务器后,在web端使用request.getRemoteAddr()(本质上就是获取\$remote_addr),取得的是nginx的地址,即\$remote_addr变量中封装的是nginx的地址,当然是没法获得用户的真实ip的。但是,nginx是可以获得用户的真实ip的,也就是说nginx使用\$remote_addr变量时获得的是用户的真实ip,如果我们想要在web端获得用户的真实ip,就必须在nginx里作一个赋值操作,即我在上面的配置: ```bash proxy_set_header X-Real-IP $remote_addr; ``` X-Forwarded-For X-Forwarded-For变量,这是一个squid开发的,用于识别通过HTTP代理或负载平衡器原始IP一个连接到Web服务器的客户机地址的非rfc标准,如果有做X-Forwarded-For设置的话,每次经过proxy转发都会有记录,格式就是client1,proxy1,proxy2以逗号隔开各个地址,由于它是非rfc标准,所以默认是没有的,需要强制添加。在默认情况下经过proxy转发的请求,在后端看来远程地址都是proxy端的ip。也就是说在默认情况下我们使用request.getAttribute("X-Forwarded-For")获取不到用户的ip,如果我们想要通过这个变量获得用户的ip,我们需要自己在nginx添加配置: ```bash proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; ``` 意思是增加一个\$proxy_add_x_forwarded_for到X-Forwarded-For里去,注意是增加,而不是覆盖,当然由于默认的X-Forwarded-For值是空的,所以我们总感觉X-Forwarded-For的值就等于\$proxy_add_x_forwarded_for的值,实际上当你搭建两台nginx在不同的ip上,并且都使用了这段配置,那你会发现在web服务器端通过request.getAttribute("X-Forwarded-For")获得的将会是客户端ip和第一台nginx的ip。 3. 那么\$proxy_add_x_forwarded_for又是什么? \$proxy_add_x_forwarded_for变量包含客户端请求头中的X-Forwarded-For与\$remote_addr两部分,他们之间用逗号分开。 举个例子,有一个web应用,在它之前通过了两个nginx转发,即用户访问该web通过两台nginx。 在第一台nginx中,使用: ```bash proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; ``` 现在的\$proxy_add_x_forwarded_for变量的X-Forwarded-For部分是空的,所以只有$remote_addr,而\$remote_addr的值是用户的ip,于是赋值以后,X-Forwarded-For变量的值就是用户的真实的ip地址了。 到了第二台nginx,使用: ```bash proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; ``` 现在的\$proxy_add_x_forwarded_for变量,X-Forwarded-For部分包含的是用户的真实ip,\$remote_addr部分的值是上一台nginx的ip地址,于是通过这个赋值以后现在的X-Forwarded-For的值就变成了“用户的真实ip,第一台nginx的ip”,这样就清楚了吧。 服务器获取真实IP,代码为: ```java public static String getIpAddress(HttpServletRequest request) { String ip = null; try { //以下两个获取在k8s中,将真实的客户端IP,放到了x-Original-Forwarded-For。而将WAF的回源地址放到了 x-Forwarded-For了。 ip = request.getHeader("X-Original-Forwarded-For"); log.info("getClientIpAddress X-Original-Forwarded-For:[{}]", ip); if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("X-Forwarded-For"); log.info("getClientIpAddress X-Forwarded-For:[{}]", ip); } //获取nginx等代理的ip if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("x-forwarded-for"); log.info("getClientIpAddress X-forwarded-For:[{}]", ip); } if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("X-Real-IP"); log.info("getClientIpAddress X-Real-IP:[{}]", ip); } if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); log.info("getClientIpAddress Proxy-Client-IP:[{}]", ip); } if (StringUtils.isEmpty(ip) || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); log.info("getClientIpAddress WL-Proxy-Client-IP:[{}]", ip); } if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); log.info("getClientIpAddress HTTP_CLIENT_IP:[{}]", ip); } if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); log.info("getClientIpAddress HTTP_X_FORWARDED_FOR:[{}]", ip); } //兼容k8s集群获取ip if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); if (LOCALHOST_IP2.equalsIgnoreCase(ip) || LOCALHOST_IP1.equalsIgnoreCase(ip)) { //从网卡获取本机IP log.info("getClientIpAddress 未使用代理,根据网卡取本机配置的IP:[{}]", ip); InetAddress iNet = null; try { iNet = InetAddress.getLocalHost(); } catch (UnknownHostException e) { log.error("getClientIpAddress error", e); } ip = iNet.getHostAddress(); } } log.info("ClientIp:[{}]", ip); } catch (Exception e) { log.error("getClientIpAddress error", e); } //使用代理,则获取第一个IP地址 if (!StringUtils.isEmpty(ip) && ip.indexOf(IP_UTILS_FLAG) > 0) { ip = ip.substring(0, ip.indexOf(IP_UTILS_FLAG)); } log.info("final ClientIp:[{}]", ip); return ip; } ``` 我们来看看各个请求头的含义: - X-Real-IP:nginx代理一般会加上此请求头。 - X-FORWARDED-FOR:这是一个Squid开发的字段,只有在通过了HTTP代理或者负载均衡服务器时才会添加该项。 - Proxy-Client-IP 和 WL-Proxy-Client-IP:这个一般是经过apache http服务器的请求才会有,用apache http做代理时一般会加上Proxy-Client-IP请求头,而WL-Proxy-Client-IP是它的weblogic插件加上的头。