测试代码
@RequestMapping("/hello1")
public Map getIp1(HttpServletRequest request) {
Enumeration<String> names = request.getHeaderNames();
ArrayList<String> array = new ArrayList<String>();
while (names.hasMoreElements()){
array.add(names.nextElement());
}
Map<String, String> map = new HashMap<>();
for (int i = 0; i < array.size(); i++) {
String key = array.get(i);
map.put(key, request.getHeader(key));
}
map.put("侯万getRemoteAddr", request.getRemoteAddr());
String 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") || ipAddress.equals("0:0:0:0:0: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(","));
}
}
map.put("ipAddress", ipAddress);
return map;
}
资料1:
如果服务器使用了Nginx
代理,需要设置Nginx
的配置,才能获取到IP,因为经过反向代理后,由于在客户端和web服务器之间增加了中间层,因此web服务器无法直接拿到客户端的ip,只能通过$remote_addr
变量拿到的将是反向代理服务器的ip地址。
我在default.conf
的配置是:
server {
listen 443 ssl; # 支持ipv4
listen [:]:443 ssl; # 支持ipv6
location /hello {
proxy_pass http://182.92.106.69:8848/hello1/;
proxy_redirect off;
# 关键的四句代码
proxy_set_header Host $host:$server_port;
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;
}
}
Nginx
中的几个变量解释:
-
$remote_addr
代表客户端的IP,但它的值不是由客户端提供的,而是服务端根据客户端的ip指定的,icanhazip的原理也是这样, 当你的浏览器访问某个网站时,假设中间没有任何代理,那么网站的web服务器就会把remote_addr
设为你在公网暴露的IP,如果你用了某个代理,那么你的浏览器会先访问这个代理,然后再由这个代理转发到网站,这样web服务器就会把remote_addr
设为这台代理机器的IP, 除非代理将你的IP附在请求header中一起转交给web服务器。 -
$proxy_add_x_forwarded_for
全局变量$proxy_add_x_forwarded_for
包含客户端请求头中的X-Forwarded-For
,与$remote_addr
两部分,他们之间用逗号分开。X-Forwarded-For
(简称XFF),X-Forwarded-For
是一个 HTTP 扩展头部。RFC 2616 协议并没有对它的定义,它最开始是由Squid
这个缓存代理软件引入,用来表示 HTTP 请求端真实 IP。如今它已经成为事实上的标准,被各大HTTP 代理、负载均衡等转发服务广泛使用,并被写入 RFC 7239(Forwarded HTTP Extension` 标准之中。 -
$proxy_set_header
,可设置代理后 header -
X-Real-IP
一般比如X-Real-IP
这一个自定义头部字段,通常被 HTTP 代理用来表示与它产生TCP 连接的设备 IP,这个设备可能是其他代理,也可能是真正的请求端,这个要看经过代理的层级次数或是是否始终将真实IP一路传下来。(牢记:任何客户端传上来的东西都是不可信的)
当多层代理或使用CDN时,如果代理服务器不把用户的真实IP传递下去,那么服务器将永远不可能获取到用户的真实IP。
资料2:
上面存在问题是,可能一个基站,或者一个小区,对外只有一个公共IP。因此这种情况下,同一ip出现数百用户也是正常的。
典型的是:一个公司内,大家共同用同一个网络,对外都是一个的公网IP。
资料3:
私有的内部组网IP段(可作为代码直接判断):
- A类:10.0.0.0 — 10.255.255.255 10.0.0.0/8
- B类:172.16.0.0 — 172.31.255.255 172.16.0.0/12
- C类:192.168.0.0 — 192.168.255.255 192.168.0.0/16
资料4:
参考文档: