Explorar el Código

未传递ip参数时获取对应客户端ip并返回解析结果

hsj hace 1 mes
padre
commit
7b7f2bf26c
Se han modificado 1 ficheros con 108 adiciones y 26 borrados
  1. 108 26
      src/main/java/xyz/hsj030208/controller/IPApiController.java

+ 108 - 26
src/main/java/xyz/hsj030208/controller/IPApiController.java

@@ -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;
+    }
 }