文章目录
ip2region
引入依赖
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>1.7.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
获取ip2region.db
文件,放在resources
https://gitcode.net/mirrors/lionsoul2014/ip2region/-/tree/master/v1.0/data
工具类
package com.example.ipdemo.utils;
import org.lionsoul.ip2region.DataBlock;
import org.lionsoul.ip2region.DbConfig;
import org.lionsoul.ip2region.DbSearcher;
import org.lionsoul.ip2region.Util;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
public class IpUtil {
/**
* 获取IP地址
* @param request
* @return
*/
public static String getIpAddr(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
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.getHeader("Proxy-Client-IP");
}
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.getRemoteAddr();
}
if ("0:0:0:0:0:0:0:1".equals(ip)) {
ip = "127.0.0.1";
}
if (ip.split(",").length > 1) {
ip = ip.split(",")[0];
}
return ip;
}
/**
* 根据IP地址获取城市
* @param ip
* @return
*/
public static String getCityInfo(String ip) {
URL url = IpUtil.class.getClassLoader().getResource("ip2region.db");
File file;
if (url != null) {
file = new File(url.getFile());
} else {
return null;
}
if (!file.exists()) {
System.out.println("Error: Invalid ip2region.db file, filePath:" + file.getPath());
return null;
}
//查询算法
int algorithm = DbSearcher.BTREE_ALGORITHM; //B-tree
//DbSearcher.BINARY_ALGORITHM //Binary
//DbSearcher.MEMORY_ALGORITYM //Memory
try {
DbConfig config = new DbConfig();
DbSearcher searcher = new DbSearcher(config, file.getPath());
Method method;
switch ( algorithm )
{
case DbSearcher.BTREE_ALGORITHM:
method = searcher.getClass().getMethod("btreeSearch", String.class);
break;
case DbSearcher.BINARY_ALGORITHM:
method = searcher.getClass().getMethod("binarySearch", String.class);
break;
case DbSearcher.MEMORY_ALGORITYM:
method = searcher.getClass().getMethod("memorySearch", String.class);
break;
default:
return null;
}
DataBlock dataBlock;
if (!Util.isIpAddress(ip)) {
System.out.println("Error: Invalid ip address");
return null;
}
dataBlock = (DataBlock) method.invoke(searcher, ip);
return dataBlock.getRegion();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
测试
其它
部署服务器后,resources下文件获取不到,是因为本地是目录结构获取,打成jar后,就获取不到了,得用InputStream
Error: Invalid ip2region.db file, filePath:file:/usr/local/download/jarPackage/ipdemo-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/ip2region.db
参考:https://blog.csdn.net/sco5282/article/details/126210398
getCityInfo
方法修改后
/**
* 根据IP地址获取城市
* @param ip
* @return
*/
public static String getCityInfo(String ip) throws IOException {
// URL url = IpUtil.class.getClassLoader().getResource("ip2region.db");
ClassPathResource classPathResource = new ClassPathResource("ip2region.db");
InputStream in = classPathResource.getInputStream();
File tmpFile = File.createTempFile("temp",".temp");
OutputStream out=new FileOutputStream(tmpFile);
byte[] data = new byte[1024];
int len ;
while ((len =in.read(data))!=-1){
out.write(data,0,len);
}
in.close();
out.close();
File file=new File(tmpFile.getAbsolutePath());
System.out.println("--------------------------------:"+file.getAbsolutePath());
if (!file.exists()) {
System.out.println("Error: Invalid ip2region.db file, filePath:" + file.getPath());
return null;
}
//查询算法
int algorithm = DbSearcher.BTREE_ALGORITHM; //B-tree
//DbSearcher.BINARY_ALGORITHM //Binary
//DbSearcher.MEMORY_ALGORITYM //Memory
try {
DbConfig config = new DbConfig();
DbSearcher searcher = new DbSearcher(config, file.getPath());
Method method;
switch ( algorithm )
{
case DbSearcher.BTREE_ALGORITHM:
method = searcher.getClass().getMethod("btreeSearch", String.class);
break;
case DbSearcher.BINARY_ALGORITHM:
method = searcher.getClass().getMethod("binarySearch", String.class);
break;
case DbSearcher.MEMORY_ALGORITYM:
method = searcher.getClass().getMethod("memorySearch", String.class);
break;
default:
return null;
}
DataBlock dataBlock;
if (!Util.isIpAddress(ip)) {
System.out.println("Error: Invalid ip address");
return null;
}
dataBlock = (DataBlock) method.invoke(searcher, ip);
return dataBlock.getRegion();
} catch (Exception e) {
e.printStackTrace();
}finally {
tmpFile.delete();
}
return null;
}
获取公网ip地址
Document doc = Jsoup.connect("http://chaipip.com/").get();
Elements eles = doc.select("#ip");
System.out.println(eles.attr("value"));
代码解析
这些都是用于获取客户端IP地址的方法或头部字段。它们之间的区别如下:
/**
* 这是一个HTTP头部字段,通常由代理服务器添加。它用于识别客户端的原始IP地址,即客户端通过的所有代理服务器的IP地址。
* 如果客户端经过多个代理服务器,那么这个字段的值会是一个以逗号分隔的IP地址列表。需要注意的是,这个字段可以被伪造,因此不一定可信。
*/
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
/**
* 这也是一个HTTP头部字段,有些代理服务器会将客户端的真实IP地址添加到这个字段中。与 x-forwarded-for 类似,它也可以被伪造,因此不一定可信。
*/
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
/**
* 这是一个过时的HTTP头部字段,以前一些代理服务器会将客户端的IP地址添加到这个字段中。目前很少使用这个字段了。
*/
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
/**
* 这是一个WebLogic服务器特定的HTTP头部字段,用于获取客户端的IP地址。它也可以被伪造,不一定可信。
*/
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
/**
* 这是一个Java Servlet API提供的方法,用于获取客户端的IP地址。它是通过底层的网络连接获取的,不依赖于任何特定的HTTP头部字段。
* 这个方法获取的IP地址是相对可靠的,但在某些情况下可能会获取到代理服务器的IP地址而不是客户端的真实IP地址。
*/
ip = request.getRemoteAddr();
}
if ("0:0:0:0:0:0:0:1".equals(ip)) {
ip = "127.0.0.1";
}
if (ip.split(",").length > 1) {
ip = ip.split(",")[0];
}
扩展
一下资料来源:https://article.itxueyuan.com/46m4nB
1.x-forwarded-for(简称:XFF) :
- X-Forwarded-for:简称XFF,它代表客户端,也就是HTTP请求的真实IP,只有在通过了HTTP代理
或者负载均衡器时才会添加该项。
产生背景
- X-Forwarded-For是用来识别 “通过HTTP代理或负载均衡方式连接到WEB服务器的客户端” 最原始的ip地址的请求字段。
- 服务端获取客户端ip地址的常用方法有两种:
Remote Address
X-Forwarded-for - 在java中,获取 客户端ip地址 最简单的方式就是 request.getRemoteAddr(),即第一种方式。
- 这种方式可以直接获取到连接服务器的客户端ip(在中间没有代理的情况下,的确是最简单有效的方式)。
- 但是当今的互联网web应用很少会将 应用服务器 直接 对外服务,有的甚至可能有多层代理。在有反向代理的情况下 直接使用 request.getRemoteAddr(),获取到的ip地址是Nginx(或其他的代理服务器的ip地址),而不是客户端的ip地址。
- 这是因为:
HTTP协议是基于TCP协议的,由于request.getRemoteAddr()获取到的是TCP层直连的客户端ip,对于web应用服务器来说直接连接它的客户端实际上是Nginx,也就是说TCP层是拿不到真实的客户端的ip的。
- 这是因为:
- 为了解决上面的问题,很多 HTTP代理 会在HTTP协议头中添加 X-Forwarded-for头,用于追踪请求来源最真实的ip地址。
XFF头格式
- 该HTTP头一般如下:
- X-Forwarded-For:真实IP,代理1的IP,代理2的IP,代理3的IP, …
XFF头ip追加原理
- 假设服务端收到的XFF信息为:X-Forwarded-For:IP0,IP1,IP2
- 那么就说明:该请求成功通过了 三台 代理服务器:proxy1,proxy2,proxy3(在这里你可能会有疑问,为什么是3台代理服务器,请继续往下看)
- 首先我们要知道,在XFF形成的过程中,代理服务器 每成功收到一个请求,就把 请求来源的IP地址 添加到右边。
- 注意:
1.添加的是 请求来源的IP地址
2.是 添加ip地址 而不是 覆盖ip地址
- 注意:
- 那么XFF头形成过程如下:
- 在请求刚从client1中发出的时候,XFF是空的
- 当请求 通过第一个代理 的时候,由于添加的是 请求来源的IP地址,所以该代理服务器会把 客户端/浏览器的IP地址(即最原始的IP地址)添加为client1;
- 当请求 通过第二个代理 的时候,由于添加的是 请求来源的IP地址,所以该代理服务器会把 第一个代理的ip地址 追加到proxy1位置上;
- 当请求 通过第三个代理 的时候,由于添加的是 请求来源的IP地址,所以该代理服务器会把 第二个代理的IP地址 追加到proxy2位置上;
- 列表中之所以没有IP3,是因为proxy3直连服务器,proxy3会给XFF追加IP2,表示它是在帮proxy2转发请求,而IP3可以在服务端通过Remote Address 字段直接获得,不需要再往 XFF头 上继续追加了。
- 从该过程便可以发现:
由于添加的是 请求来源的IP地址,所以导致在 第一个代理处 添加 最原始的ip地址 ,在 第二个代理处添加 第一个代理的ip地址… 这样的错位的关系。
public String getClientIp(HttpServletRequest request) {
String xff = request.getHeader("X-Forwarded-For");
if (xff == null) {
return request.getRemoteAddr();
} else {
return xff.contains(",") ? xff.split(",")[0] : xff;
}
}
//另外,要让Nginx支持X-Forwarded-For头,需要配置:proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- 以上就是一种常用的获取客户端真实ip的方法:
- 首先从HTTP头中获取X-Forwarded-For,如果X-Forwarded-For头存在就按逗号分隔取
最左边
第一个ip的地址,不存在就直接通过request.getRemoteAddr()获取ip地址
- 首先从HTTP头中获取X-Forwarded-For,如果X-Forwarded-For头存在就按逗号分隔取
- 注意:在使用X-Forwarded-For获取ip时,默认获取的是最左边的值(即client1)
利用X-Forwarded-For伪造客户端ip漏洞
- 首先,我们要知道 Remote Address 是无法伪造的,因为它是直接从TCP连接信息中获取到的,而建立TCP连接需要三次握手。如果伪造了源ip,便无法建立TCP连接,更不会有后面的HTTP请求。
- 但是,X-Forwarded-For头是可以伪造的。一般的客户端(如:浏览器)在发送请求的时候是没有X-Forwarded-For头的,当请求到达第一个代理服务器的时候,代理服务器会加上X-Forwarded-For请求头,并将值设为客户端的ip地址(也就是最左边的第一个值),后面如果还有多个代理,则会依次将ip追加到X-Forwarded-For都的最右边,最终当请求到达web应用服务器时,应用通过获取X-Forwarded-For头取左边第一个ip即为客户端真实ip
- 但是如果客户端在发送请求时,就在请求头上带上一个伪造的X-Forwarded-For,由于后续的每层代理只会追加而不会覆盖ip,那么最终到达应用服务器时,获取的最左边第一个ip地址将会是客户端伪造的ip。也就是上面的java代码中getClientIp()方法获取的ip地址很有可能是伪造的ip地址。
- 该漏洞如果没有加以限制,则会造成巨大的安全隐患
- 可以利用工具Postman或burpsuite进行XFF攻击
2.X-Real-IP :这也是一个HTTP头部字段,有些代理服务器会将客户端的真实IP地址添加到这个字段中。与 x-forwarded-for 类似,它也可以被伪造,因此不一定可信。
3.Proxy-Client-IP :这是一个过时的HTTP头部字段,以前一些代理服务器会将客户端的IP地址添加到这个字段中。目前很少使用这个字段了。
4.WL-Proxy-Client-IP :这是一个WebLogic服务器特定的HTTP头部字段,用于获取客户端的IP地址。它也可以被伪造,不一定可信。
5.request.getRemoteAddr() :这是一个Java Servlet API提供的方法,用于获取客户端的IP地址。它是通过底层的网络连接获取的,不依赖于任何特定的HTTP头部字段。这个方法获取的IP地址是相对可靠的,在中间没有代理的情况下,的确是最简单有效的方式,但是当今的互联网web应用很少会将 应用服务器 直接 对外服务,有的甚至可能有多层代理。在有反向代理的情况下 直接使用 request.getRemoteAddr(),获取到的ip地址是Nginx(或其他的代理服务器的ip地址),而不是客户端的ip地址。
这是因为:
HTTP协议是基于TCP协议的,由于request.getRemoteAddr()获取到的是TCP层直连的客户端ip,对于web应用服务器来说直接连接它的客户端实际上是Nginx,也就是说TCP层是拿不到真实的客户端的ip的。为了解决上面的问题,很多 HTTP代理 会在HTTP协议头中添加 X-Forwarded-for头,用于追踪请求来源最真实的ip地址。
总结来说, x-forwarded-for 和 X-Real-IP 是常用的获取客户端IP地址的头部字段,但它们可以被伪造,因此不一定可信。而 Proxy-Client-IP 和 WL-Proxy-Client-IP 是一些过时或特定于某些服务器的头部字段,使用较少。 request.getRemoteAddr() 是通过底层网络连接获取IP地址的方法,相对可靠。在实际使用中,应根据具体情况选择适合的方法来获取客户端的IP地址。