|
|
@@ -1,5 +1,6 @@
|
|
|
package xyz.hsj030208.controller;
|
|
|
|
|
|
+import jakarta.servlet.http.HttpServletRequest;
|
|
|
import jakarta.servlet.http.HttpServletResponse;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.jsoup.Jsoup;
|
|
|
@@ -17,6 +18,7 @@ import java.io.IOException;
|
|
|
import java.io.PrintWriter;
|
|
|
import java.net.InetAddress;
|
|
|
import java.net.UnknownHostException;
|
|
|
+import java.util.regex.Pattern;
|
|
|
|
|
|
@RestController
|
|
|
@RequestMapping("/ip")
|
|
|
@@ -24,8 +26,11 @@ import java.net.UnknownHostException;
|
|
|
public class IPApiController {
|
|
|
|
|
|
@GetMapping("/ip.cn/get")
|
|
|
- public ApiDetail getIpDetail(@RequestParam String ip, HttpServletResponse response) throws IOException {
|
|
|
- if (!isValidIPAddress(ip)) {
|
|
|
+ public ApiDetail getIpDetail(@RequestParam(required = false) String ip, HttpServletResponse response, HttpServletRequest request) throws IOException {
|
|
|
+ if (ip == null) {
|
|
|
+ ip = getClientIpAddress(request);
|
|
|
+ }
|
|
|
+ if (!isValidIPAddress(ip) || !isPublicIPv4(ip)) {
|
|
|
log.error("参数错误,无法解析成ip");
|
|
|
try {
|
|
|
// 设置HTTP状态码为400(Bad Request)
|
|
|
@@ -44,6 +49,7 @@ public class IPApiController {
|
|
|
}
|
|
|
return null;
|
|
|
}
|
|
|
+ log.info("获取ip详情: {}", ip);
|
|
|
String url = "https://ip.cn/ip/" + ip + ".html";
|
|
|
ApiDetail apiDetail = new ApiDetail();
|
|
|
apiDetail.setIp(ip);
|
|
|
@@ -104,28 +110,104 @@ public class IPApiController {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// @GetMapping("/get/")
|
|
|
-// public ApiDetail getIpDetail(String ip, HttpServletResponse response) {
|
|
|
-// HttpClient client = HttpClient.newHttpClient();
|
|
|
-//
|
|
|
-// HttpRequest request = HttpRequest.newBuilder()
|
|
|
-// .uri(URI.create("https://rest.ipw.cn/api/ip/query?ip=" + ip + "&lang=zh"))
|
|
|
-// .GET()
|
|
|
-// .setHeader("accept", "application/json, text/plain, */*")
|
|
|
-// .setHeader("accept-language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
|
|
|
-// .setHeader("cache-control", "no-cache")
|
|
|
-// .setHeader("origin", "https://ipw.cn")
|
|
|
-// .setHeader("pragma", "no-cache")
|
|
|
-// .setHeader("priority", "u=1, i")
|
|
|
-// .setHeader("referer", "https://ipw.cn/")
|
|
|
-// .setHeader("sec-ch-ua", "\"Microsoft Edge\";v=\"143\", \"Chromium\";v=\"143\", \"Not A(Brand\";v=\"24\"")
|
|
|
-// .setHeader("sec-ch-ua-mobile", "?0")
|
|
|
-// .setHeader("sec-ch-ua-platform", "\"Windows\"")
|
|
|
-// .setHeader("sec-fetch-dest", "empty")
|
|
|
-// .setHeader("sec-fetch-mode", "cors")
|
|
|
-// .setHeader("sec-fetch-site", "same-site")
|
|
|
-// .setHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0")
|
|
|
-// .build();
|
|
|
-// HttpResponse<String> jsonResponse = client.send(request, HttpResponse.BodyHandlers.ofString());
|
|
|
-// }
|
|
|
+ /**
|
|
|
+ * 从HttpServletRequest中获取客户端的真实IP地址
|
|
|
+ * 检查顺序: X-Forwarded-For -> Proxy-Client-IP -> WL-Proxy-Client-IP -> HTTP_CLIENT_IP -> HTTP_X_FORWARDED_FOR -> RemoteAddr
|
|
|
+ * @param request HttpServletRequest对象
|
|
|
+ * @return 客户端真实IP,如果无法获取则返回null
|
|
|
+ */
|
|
|
+ public String getClientIpAddress(HttpServletRequest request) {
|
|
|
+ if (request == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ String ipAddress = null;
|
|
|
+ // 1. 优先检查X-Forwarded-For头(标准代理头)
|
|
|
+ ipAddress = request.getHeader("X-Forwarded-For");
|
|
|
+ if (isValidIPAddress(ipAddress)) {
|
|
|
+ // X-Forwarded-For可能包含多个IP(客户端IP,代理IP1,代理IP2),取第一个
|
|
|
+ int commaIndex = ipAddress.indexOf(',');
|
|
|
+ if (commaIndex > 0) {
|
|
|
+ ipAddress = ipAddress.substring(0, commaIndex).trim();
|
|
|
+ }
|
|
|
+ return ipAddress;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 检查其他可能的代理头
|
|
|
+ String[] headersToCheck = {
|
|
|
+ "Proxy-Client-IP",
|
|
|
+ "WL-Proxy-Client-IP",
|
|
|
+ "HTTP_CLIENT_IP",
|
|
|
+ "HTTP_X_FORWARDED_FOR"
|
|
|
+ };
|
|
|
+
|
|
|
+ for (String header : headersToCheck) {
|
|
|
+ ipAddress = request.getHeader(header);
|
|
|
+ if (isValidIPAddress(ipAddress)) {
|
|
|
+ return ipAddress;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 最后使用默认的远程地址
|
|
|
+ ipAddress = request.getRemoteAddr();
|
|
|
+ if (isValidIPAddress(ipAddress)) {
|
|
|
+ // 处理本地回环地址
|
|
|
+ if ("0:0:0:0:0:0:0:1".equals(ipAddress) || "::1".equals(ipAddress)) {
|
|
|
+ return "127.0.0.1";
|
|
|
+ }
|
|
|
+ return ipAddress;
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断给定的IPv4地址是否为公网IP。
|
|
|
+ * @param ipAddress 待验证的IP地址字符串
|
|
|
+ * @return 如果是公网IP返回 true;如果是私有IP、环回地址、格式无效或为null则返回 false
|
|
|
+ */
|
|
|
+ public boolean isPublicIPv4(String ipAddress) {
|
|
|
+ Pattern IPV4_PATTERN = Pattern.compile(
|
|
|
+ "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
|
|
|
+ );
|
|
|
+ // 1. 基础检查
|
|
|
+ if (ipAddress == null || ipAddress.isEmpty()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ipAddress = ipAddress.trim();
|
|
|
+
|
|
|
+ // 2. 验证IPv4格式
|
|
|
+ if (!IPV4_PATTERN.matcher(ipAddress).matches()) {
|
|
|
+ return false; // 格式无效
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 将IP地址按"."分割成四个部分
|
|
|
+ String[] ipParts = ipAddress.split("\\.");
|
|
|
+ int firstOctet = Integer.parseInt(ipParts[0]);
|
|
|
+ int secondOctet = Integer.parseInt(ipParts[1]);
|
|
|
+
|
|
|
+ // 4. 判断是否为私有IP或环回地址
|
|
|
+ // 4.1 检查10.0.0.0/8
|
|
|
+ if (firstOctet == 10) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ // 4.2 检查172.16.0.0/12
|
|
|
+ if (firstOctet == 172 && secondOctet >= 16 && secondOctet <= 31) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ // 4.3 检查192.168.0.0/16
|
|
|
+ if (firstOctet == 192 && secondOctet == 168) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ // 4.4 检查环回地址127.0.0.0/8
|
|
|
+ if (firstOctet == 127) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 如果以上都不是,则认为是公网IP
|
|
|
+ // 注意:这里没有严格判断地址是否在公认的可路由公网IP段内(如1.0.0.0至223.255.255.255)[1,7](@ref)
|
|
|
+ // 因为该方法主要目的是排除已知的私有和保留地址。
|
|
|
+ return true;
|
|
|
+ }
|
|
|
}
|