java获取客户端的ip地址以及获取位置
2024-11-04 14:58:19 # 技术笔记

主要依赖:

1
2
3
4
5
6
7
8
9
10
11
12
<!--通过ip查询客户端位置-->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.7.0</version>
</dependency>
<!-- Alibaba Fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>

首先先获取到访问者的ip地址

访问必须是通过公网访问,要是内网互ip访问相测试的话,会返回127.0.0.1,或者访问者的内网ip地址。

通过X-FORWARDED-FOR等信息。跟踪原有的客户端IP地址和原来客户端请求的服务器地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package com.wonder.utils;


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

public class getIPutils {
public static String resClientIP(HttpServletRequest request) {
String ip = getClientIpAddress(request);
boolean isInner = isInnerIP(ip);
if (!isInner) {
return ip;
}else {
return "内网ip";
}

}
public static String getClientIpAddress(HttpServletRequest request) {
// 获取请求主机IP地址,如果通过代理进来,则透过防火墙获取真实IP地址
String headerName = "x-forwarded-for";
String ip = request.getHeader(headerName);
if (null != ip && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个IP值,第一个IP才是真实IP,它们按照英文逗号','分割
if (ip.indexOf(",") != -1) {
ip = ip.split(",")[0];
}
}
if (checkIp(ip)) {
headerName = "Proxy-Client-IP";
ip = request.getHeader(headerName);
}
if (checkIp(ip)) {
headerName = "WL-Proxy-Client-IP";
ip = request.getHeader(headerName);
}
if (checkIp(ip)) {
headerName = "HTTP_CLIENT_IP";
ip = request.getHeader(headerName);
}
if (checkIp(ip)) {
headerName = "HTTP_X_FORWARDED_FOR";
ip = request.getHeader(headerName);
}
if (checkIp(ip)) {
headerName = "X-Real-IP";
ip = request.getHeader(headerName);
}
if (checkIp(ip)) {
headerName = "remote addr";
ip = request.getRemoteAddr();
// 127.0.0.1 ipv4, 0:0:0:0:0:0:0:1 ipv6
if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip)) {
//根据网卡取本机配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ip = inet.getHostAddress();
}
}
System.out.println("getClientIp IP is " + ip + ", headerName = " + headerName);
return ip;
}
private static boolean checkIp(String ip) {
if (null == ip || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
return true;
}
return false;
}
/**
* 判断IP是否是内网地址
* @param ipAddress ip地址
* @return 是否是内网地址
*/
public static boolean isInnerIP(String ipAddress) {
boolean isInnerIp;
long ipNum = getIpNum(ipAddress);
/**
私有IP:A类 10.0.0.0-10.255.255.255
B类 172.16.0.0-172.31.255.255
C类 192.168.0.0-192.168.255.255
还有127这个网段是环回地址
**/
long aBegin = getIpNum("10.0.0.0");
long aEnd = getIpNum("10.255.255.255");

long bBegin = getIpNum("172.16.0.0");
long bEnd = getIpNum("172.31.255.255");

long cBegin = getIpNum("192.168.0.0");
long cEnd = getIpNum("192.168.255.255");
isInnerIp = isInner(ipNum, aBegin, aEnd) || isInner(ipNum, bBegin, bEnd) || isInner(ipNum, cBegin, cEnd)
|| ipAddress.equals("127.0.0.1");
return isInnerIp;
}

private static long getIpNum(String ipAddress) {
String[] ip = ipAddress.split("\\.");
long a = Integer.parseInt(ip[0]);
long b = Integer.parseInt(ip[1]);
long c = Integer.parseInt(ip[2]);
long d = Integer.parseInt(ip[3]);

return a * 256 * 256 * 256 + b * 256 * 256 + c * 256 + d;
}

private static boolean isInner(long userIp, long begin, long end) {
return (userIp >= begin) && (userIp <= end);
}

public static String getRealIP(HttpServletRequest request){
// 获取客户端ip地址
String clientIp = request.getHeader("x-forwarded-for");

if (clientIp == null || clientIp.length() == 0 || "unknown".equalsIgnoreCase(clientIp)) {
clientIp = request.getRemoteAddr();
}

String[] clientIps = clientIp.split(",");
if(clientIps.length <= 1) return clientIp.trim();

// 判断是否来自CDN
if(isComefromCDN(request)){
if(clientIps.length>=2) return clientIps[clientIps.length-2].trim();
}

return clientIps[clientIps.length-1].trim();
}

private static boolean isComefromCDN(HttpServletRequest request) {
String host = request.getHeader("host");
return host.contains("www.189.cn") ||host.contains("shouji.189.cn") || host.contains(
"image2.chinatelecom-ec.com") || host.contains(
"image1.chinatelecom-ec.com");
}

}

相关请求头

  • X-Forwarded-For 记录一个请求从客户端出发到目标服务器过程中经历的代理,或者负载平衡设备的IP。这是由缓存代理软件 Squid 引入,用来表示 HTTP 请求端真实 IP,现在已经成为事实上的标准,被各大 HTTP 代理、负载均衡等转发服务广泛使用,并被写入 RFC 7239(Forwarded HTTP Extension)标准之中。格式为X-Forwarded-For:client1,proxy1,proxy2,一般情况下,第一个ip为客户端真实ip,后面的为经过的代理服务器的ip。现在大部分的代理都会加上这个请求头。
  • Proxy-Client-IP/WLProxy-Client-IP 这个一般是经过apache http服务器的请求才会有,用apache http做代理时一般会加上Proxy-Client-IP请求头,而WL-Proxy-Client-IP是他的weblogic插件加上的头。
  • HTTP_CLIENT_IP 有些代理服务器会加上此请求头。
  • X-Real-IP nginx代理一般会加上此请求头。

通过ip获取位置

通过上面返还的ip地址进行解析,具体方式可以参考Ip2region的官方文档。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.wonder.utils;

import com.alibaba.fastjson.JSONObject;
import org.lionsoul.ip2region.xdb.Searcher;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;


public class FromIpToCtiy {
//https://github.com/lionsoul2014/ip2region/tree/master/binding/java
public static String resLocation(String ip) {
String dbPath = "src/main/resources/ip2region.xdb";
// 1、从 dbPath 中预先加载 VectorIndex 缓存,并且把这个得到的数据作为全局变量,后续反复使用。
byte[] vIndex;
try {
vIndex = Searcher.loadVectorIndexFromFile(dbPath);
} catch (Exception e) {
System.out.printf("failed to load vector index from `%s`: %s\n", dbPath, e);
return "{\"error\":\"加载索引失败\"}";
}

// 2、使用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。
Searcher searcher;
try {
searcher = Searcher.newWithVectorIndex(dbPath, vIndex);
} catch (Exception e) {
System.out.printf("failed to create vectorIndex cached searcher with `%s`: %s\n", dbPath, e);
return "{\"error\":\"创建缓存搜索器失败\"}";
}
// 3、查询
long sTime;
String region = null;
long cost = 0;
try {
sTime= System.nanoTime();
region= searcher.search(ip);
cost= TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime));
System.out.printf("{region: %s, ioCount: %d, took: %d μs}\n", region, searcher.getIOCount(), cost);
// 4、关闭资源
searcher.close();
} catch (Exception e) {
System.out.printf("failed to search(%s): %s\n", ip, e);
}
Map<String,String> resmap = new HashMap<>();
resmap.put("location",region);
resmap.put("ioCount", String.valueOf(searcher.getIOCount()));
resmap.put("cost", cost+"μs");
return JSONObject.toJSONString(resmap);
// 备注:每个线程需要单独创建一个独立的 Searcher 对象,但是都共享全局的制度 vIndex 缓存。
}
}

说实在的,其实没啥技术含量,就是互相调用查看的,通过客户端的请求进行解析。然后查询地址的,也就是通过一个人家写的库进行查询。很简单的。欸。。目前没啥好想法了。