最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot中獲取真實客戶端IP的終極方案

 更新時間:2026年01月07日 09:46:09   作者:往事隨風去  
在日常開發(fā)中,獲取客戶端IP看似簡單,實則暗藏玄機,今天,小編將徹底揭秘Spring Boot中獲取真實客戶端IP的正確姿勢,讓你避開所有的坑

引言:為什么你的IP獲取方式可能是錯的?

在日常開發(fā)中,獲取客戶端IP看似簡單,實則暗藏玄機。很多開發(fā)者直接使用request.getRemoteAddr(),結(jié)果在生產(chǎn)環(huán)境中發(fā)現(xiàn)獲取到的都是負載均衡器的IP,而非真實用戶IP。更糟糕的是,有些方案存在安全漏洞,可能被惡意用戶偽造IP地址。

今天,我將徹底揭秘Spring Boot中獲取真實客戶端IP的正確姿勢,讓你避開所有坑!

一、理解IP傳遞的底層原理

1.1 為什么需要特殊處理

在現(xiàn)代Web架構(gòu)中,請求往往要經(jīng)過多個中間件:

用戶 → CDN → 負載均衡器 → 網(wǎng)關(guān) → 應用服務器

每個環(huán)節(jié)都會修改請求信息,導致簡單的getRemoteAddr()失效。

1.2 關(guān)鍵HTTP頭字段解析

頭字段含義示例可信度
X-Forwarded-For代理鏈IP序列1.2.3.4, 5.6.7.8????
X-Real-IP最后一個代理IP1.2.3.4???
Proxy-Client-IPApache代理IP1.2.3.4??
WL-Proxy-Client-IPWebLogic代理IP1.2.3.4??

1.3 X-Forwarded-For的深度解析

這才是重點! X-Forwarded-For是獲取真實IP的關(guān)鍵,但很多人用錯了!

X-Forwarded-For: 客戶端真實IP, 代理服務器1IP, 代理服務器2IP, ...

重要規(guī)則:

  • 最左邊的IP是原始客戶端IP
  • 后續(xù)IP是經(jīng)過的代理服務器IP
  • 多個IP用逗號分隔

實際場景示例:

// 場景1:直接訪問(無代理)
X-Forwarded-For: null

// 場景2:經(jīng)過CDN
X-Forwarded-For: 123.45.67.89

// 場景3:CDN + Nginx負載均衡  
X-Forwarded-For: 123.45.67.89, 10.0.1.100

// 場景4:復雜代理鏈
X-Forwarded-For: 123.45.67.89, 203.0.113.195, 198.51.100.10

二、終極解決方案:安全可靠的IP工具類

下面這個工具類經(jīng)過生產(chǎn)環(huán)境千錘百煉,直接復制使用即可!

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * IP工具類 - 獲取真實客戶端IP地址
 * 支持多級代理、防止IP偽造、安全可靠
 */
public class IpUtils {
    
    private static final String UNKNOWN = "unknown";
    private static final String LOCALHOST_IP = "127.0.0.1";
    private static final String LOCALHOST_IPV6 = "0:0:0:0:0:0:0:1";
    private static final String SEPARATOR = ",";
    
    // 內(nèi)網(wǎng)IP段(用于識別代理服務器)
    private static final Set<String> INTERNAL_IP_SEGMENTS = new HashSet<>(Arrays.asList(
        "10.", "192.168.", "172.16.", "172.17.", "172.18.", "172.19.", 
        "172.20.", "172.21.", "172.22.", "172.23.", "172.24.", "172.25.", 
        "172.26.", "172.27.", "172.28.", "172.29.", "172.30.", "172.31."
    ));
    
    /**
     * 獲取真實客戶端IP(推薦使用)
     * 安全可靠,防止偽造,支持多級代理
     */
    public static String getClientRealIp(HttpServletRequest request) {
        // 1. 優(yōu)先檢查X-Forwarded-For(處理多級代理)
        String ip = parseXForwardedFor(request.getHeader("X-Forwarded-For"));
        if (isValidPublicIp(ip)) {
            return ip;
        }
        
        // 2. 檢查其他代理頭
        ip = getIpFromHeaders(request);
        if (isValidPublicIp(ip)) {
            return ip;
        }
        
        // 3. 最后使用RemoteAddr
        ip = request.getRemoteAddr();
        return LOCALHOST_IPV6.equals(ip) ? LOCALHOST_IP : ip;
    }
    
    /**
     * 解析X-Forwarded-For頭(核心邏輯)
     */
    private static String parseXForwardedFor(String xffHeader) {
        if (xffHeader == null || xffHeader.trim().isEmpty()) {
            return null;
        }
        
        String[] ips = xffHeader.split(SEPARATOR);
        
        // 從右向左查找第一個公網(wǎng)IP(更安全)
        for (int i = ips.length - 1; i >= 0; i--) {
            String ip = ips[i].trim();
            if (isValidIp(ip) && !isInternalIp(ip)) {
                return ip;
            }
        }
        
        // 如果沒有公網(wǎng)IP,返回第一個有效IP
        for (String ip : ips) {
            String trimmedIp = ip.trim();
            if (isValidIp(trimmedIp)) {
                return trimmedIp;
            }
        }
        
        return null;
    }
    
    /**
     * 從其他頭字段獲取IP
     */
    private static String getIpFromHeaders(HttpServletRequest request) {
        String[] headers = {
            "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP",
            "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"
        };
        
        for (String header : headers) {
            String ip = request.getHeader(header);
            if (isValidIp(ip)) {
                return ip;
            }
        }
        return null;
    }
    
    /**
     * 驗證IP是否有效
     */
    private static boolean isValidIp(String ip) {
        return ip != null && 
               !ip.isEmpty() && 
               !UNKNOWN.equalsIgnoreCase(ip) &&
               isValidIpAddress(ip);
    }
    
    /**
     * 驗證是否為公網(wǎng)IP
     */
    private static boolean isValidPublicIp(String ip) {
        return isValidIp(ip) && !isInternalIp(ip) && !isLocalhost(ip);
    }
    
    /**
     * 檢查是否為內(nèi)網(wǎng)IP
     */
    private static boolean isInternalIp(String ip) {
        if (ip == null) return false;
        return INTERNAL_IP_SEGMENTS.stream().anyMatch(ip::startsWith);
    }
    
    /**
     * 檢查是否為本地地址
     */
    private static boolean isLocalhost(String ip) {
        return LOCALHOST_IP.equals(ip) || LOCALHOST_IPV6.equals(ip);
    }
    
    /**
     * 驗證IP地址格式
     */
    public static boolean isValidIpAddress(String ip) {
        if (ip == null || ip.isEmpty()) return false;
        
        // IPv4驗證
        String ipv4Pattern = "^((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]?)$";
        if (ip.matches(ipv4Pattern)) return true;
        
        // IPv6簡化驗證
        if (ip.contains(":")) return true;
        
        return false;
    }
}

三、Spring Boot配置:讓服務器認識代理

3.1 Tomcat代理配置

@Configuration
public class TomcatProxyConfig {
    
    /**
     * 配置Tomcat識別代理頭
     */
    @Bean
    public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatProxyCustomizer() {
        return factory -> factory.addConnectorCustomizers(connector -> {
            connector.setProperty("relaxedQueryChars", "|{}[]");
            connector.setProperty("relaxedPathChars", "|{}[]");
            connector.setProperty("remoteIpHeader", "x-forwarded-for");
            connector.setProperty("protocolHeader", "x-forwarded-proto");
            // 信任的內(nèi)網(wǎng)代理(根據(jù)實際情況調(diào)整)
            connector.setProperty("internalProxies", 
                "192\\.168\\.\\d{1,3}\\.\\d{1,3}|10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|172\\.(1[6-9]|2[0-9]|3[0-1])\\.\\d{1,3}\\.\\d{1,3}");
        });
    }
}

3.2 應用配置文件

# application.yml
server:
  tomcat:
    remoteip:
      remote-ip-header: x-forwarded-for
      protocol-header: x-forwarded-proto
      internal-proxies: |
        192\.168\.\d{1,3}\.\d{1,3}|10\.\d{1,3}\.\d{1,3}\.\d{1,3}|
        172\.(1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3}
      
spring:
  mvc:
    log-request-details: true

四、高級功能:IP攔截與安全防護

4.1 IP攔截器(自動記錄)

@Component
public class IpLoggingInterceptor implements HandlerInterceptor {
    
    private static final Logger logger = LoggerFactory.getLogger(IpLoggingInterceptor.class);
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) {
        
        String clientIp = IpUtils.getClientRealIp(request);
        request.setAttribute("clientRealIp", clientIp);
        
        // 記錄訪問日志
        logger.info("客戶端訪問: IP={}, URI={}, User-Agent={}", 
                   clientIp, 
                   request.getRequestURI(),
                   request.getHeader("User-Agent"));
        
        return true;
    }
}

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    
    @Autowired
    private IpLoggingInterceptor ipLoggingInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(ipLoggingInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/health", "/metrics");
    }
}

4.2 IP安全過濾器(防刷/黑名單)

@Component
@Order(1)
public class IpSecurityFilter implements Filter {
    
    // IP黑名單(可從數(shù)據(jù)庫或配置中心加載)
    private final Set<String> blacklistedIps = ConcurrentHashMap.newKeySet();
    
    // IP訪問頻率限制(簡單的內(nèi)存實現(xiàn),生產(chǎn)環(huán)境建議用Redis)
    private final Map<String, RateLimitInfo> rateLimitMap = new ConcurrentHashMap<>();
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String clientIp = IpUtils.getClientRealIp(httpRequest);
        
        // 1. 黑名單檢查
        if (blacklistedIps.contains(clientIp)) {
            logSecurityEvent("IP黑名單攔截", clientIp, httpRequest);
            sendErrorResponse(response, 403, "您的IP已被禁止訪問");
            return;
        }
        
        // 2. 頻率限制檢查
        if (isRateLimited(clientIp)) {
            logSecurityEvent("頻率限制攔截", clientIp, httpRequest);
            sendErrorResponse(response, 429, "訪問過于頻繁,請稍后再試");
            return;
        }
        
        // 3. 可疑行為檢測
        if (isSuspiciousRequest(clientIp, httpRequest)) {
            logSecurityEvent("可疑請求攔截", clientIp, httpRequest);
            blacklistedIps.add(clientIp); // 自動加入黑名單
            sendErrorResponse(response, 403, "檢測到異常訪問行為");
            return;
        }
        
        chain.doFilter(request, response);
    }
    
    private boolean isRateLimited(String ip) {
        RateLimitInfo info = rateLimitMap.computeIfAbsent(ip, k -> new RateLimitInfo());
        long currentTime = System.currentTimeMillis();
        
        // 限制規(guī)則:每分鐘最多60次請求
        if (currentTime - info.getWindowStart() > 60000) {
            info.reset(60, currentTime);
        }
        
        return !info.tryAcquire();
    }
    
    private boolean isSuspiciousRequest(String ip, HttpServletRequest request) {
        // 檢測異常User-Agent
        String userAgent = request.getHeader("User-Agent");
        if (userAgent == null || userAgent.trim().isEmpty()) {
            return true;
        }
        
        // 檢測常見攻擊特征
        String uri = request.getRequestURI().toLowerCase();
        if (uri.contains("admin") || uri.contains("phpmyadmin") || 
            uri.contains("wp-admin") || uri.contains("shell")) {
            return true;
        }
        
        return false;
    }
    
    private void sendErrorResponse(ServletResponse response, int status, String message) 
            throws IOException {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setStatus(status);
        httpResponse.setContentType("application/json;charset=utf-8");
        httpResponse.getWriter().write("{\"code\": " + status + ", \"message\": \"" + message + "\"}");
    }
    
    private void logSecurityEvent(String event, String ip, HttpServletRequest request) {
        logger.warn("安全事件: {} - IP: {}, URI: {}, User-Agent: {}", 
                   event, ip, request.getRequestURI(), request.getHeader("User-Agent"));
    }
    
    // 頻率限制內(nèi)部類
    private static class RateLimitInfo {
        private int tokens;
        private long windowStart;
        private final int maxTokens = 60;
        
        RateLimitInfo() {
            reset(maxTokens, System.currentTimeMillis());
        }
        
        void reset(int tokens, long windowStart) {
            this.tokens = tokens;
            this.windowStart = windowStart;
        }
        
        boolean tryAcquire() {
            if (tokens > 0) {
                tokens--;
                return true;
            }
            return false;
        }
        
        long getWindowStart() {
            return windowStart;
        }
    }
}

五、實戰(zhàn)測試:驗證你的IP獲取是否正確

5.1 調(diào)試控制器

@RestController
@RequestMapping("/debug")
public class IpDebugController {
    
    @GetMapping("/ip")
    public Map<String, Object> debugIp(HttpServletRequest request) {
        Map<String, Object> result = new LinkedHashMap<>();
        
        // 真實IP
        result.put("真實客戶端IP", IpUtils.getClientRealIp(request));
        
        // 各種頭字段對比
        result.put("RemoteAddr", request.getRemoteAddr());
        result.put("X-Forwarded-For", request.getHeader("X-Forwarded-For"));
        result.put("X-Real-IP", request.getHeader("X-Real-IP"));
        result.put("Proxy-Client-IP", request.getHeader("Proxy-Client-IP"));
        result.put("WL-Proxy-Client-IP", request.getHeader("WL-Proxy-Client-IP"));
        
        // 請求詳細信息
        result.put("請求方法", request.getMethod());
        result.put("請求URI", request.getRequestURI());
        result.put("User-Agent", request.getHeader("User-Agent"));
        
        return result;
    }
    
    @GetMapping("/ip-headers")
    public Map<String, String> getAllIpHeaders(HttpServletRequest request) {
        Map<String, String> headers = new LinkedHashMap<>();
        
        String[] ipHeaders = {
            "X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", 
            "WL-Proxy-Client-IP", "HTTP_X_FORWARDED_FOR", "HTTP_X_FORWARDED",
            "HTTP_X_CLUSTER_CLIENT_IP", "HTTP_CLIENT_IP", "HTTP_FORWARDED_FOR",
            "HTTP_FORWARDED", "HTTP_VIA", "REMOTE_ADDR"
        };
        
        for (String header : ipHeaders) {
            String value = request.getHeader(header);
            if (value != null && !value.trim().isEmpty()) {
                headers.put(header, value);
            }
        }
        
        return headers;
    }
}

5.2 測試用例

@SpringBootTest
class IpUtilsTest {
    
    @Test
    void testGetClientRealIp() {
        // 模擬HttpServletRequest
        MockHttpServletRequest request = new MockHttpServletRequest();
        
        // 測試場景1:直接訪問
        request.setRemoteAddr("123.45.67.89");
        assertEquals("123.45.67.89", IpUtils.getClientRealIp(request));
        
        // 測試場景2:單層代理
        request.addHeader("X-Forwarded-For", "123.45.67.89");
        request.setRemoteAddr("10.0.0.1");
        assertEquals("123.45.67.89", IpUtils.getClientRealIp(request));
        
        // 測試場景3:多層代理
        request.addHeader("X-Forwarded-For", "123.45.67.89, 10.0.1.100, 10.0.1.101");
        assertEquals("123.45.67.89", IpUtils.getClientRealIp(request));
        
        // 測試場景4:IPv6
        request.addHeader("X-Forwarded-For", "2001:db8::1");
        assertEquals("2001:db8::1", IpUtils.getClientRealIp(request));
    }
}

六、生產(chǎn)環(huán)境最佳實踐

6.1 配置管理

  • 將信任的代理IP列表配置在配置中心,支持動態(tài)更新
  • 為不同環(huán)境(開發(fā)、測試、生產(chǎn))設(shè)置不同的代理配置

6.2 監(jiān)控告警

@Component
public class IpMonitor {
    
    @EventListener
    public void handleBlacklistEvent(BlacklistEvent event) {
        // 發(fā)送告警通知
        alertService.sendAlert("IP黑名單新增: " + event.getIp());
    }
    
    @Scheduled(fixedRate = 300000) // 5分鐘執(zhí)行一次
    public void cleanupRateLimit() {
        // 定期清理過期的頻率限制記錄
    }
}

6.3 性能優(yōu)化

  • 對于高并發(fā)場景,使用Redis實現(xiàn)分布式頻率限制
  • 對IP查詢結(jié)果進行適當緩存(注意緩存時間不宜過長)

七、常見問題排查

Q1: 為什么獲取到的還是127.0.0.1?

A: 檢查負載均衡器是否正確配置了X-Forwarded-For頭。

Q2: 多級代理下如何確定真實IP?

A: 使用本文提供的parseXForwardedFor方法,它會自動處理多級代理情況。

Q3: 如何防止IP偽造?

A: 配置internal-proxies只信任內(nèi)部代理服務器,不信任客戶端傳遞的頭部。

總結(jié)

獲取真實客戶端IP是Web開發(fā)中的基礎(chǔ)但重要的工作。通過本文的完整方案,你可以:

  • 正確獲取多級代理后的真實客戶端IP
  • 有效防止IP地址偽造攻擊
  • 實現(xiàn)IP級別的安全防護
  • 具備完整的監(jiān)控和調(diào)試能力

記住:不要相信客戶端傳遞的任何信息,始終通過可信的代理服務器頭字段來獲取真實IP。

以上就是SpringBoot中獲取真實客戶端IP的終極方案的詳細內(nèi)容,更多關(guān)于SpringBoot獲取客戶端IP的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論

商都县| 唐山市| 黄石市| 巫山县| 庆云县| 高安市| 宝坻区| 白银市| 东港市| 星座| 石嘴山市| 江山市| 新宾| 德格县| 安徽省| 三都| 山阴县| 玉环县| 唐山市| 富阳市| 桃园市| 东宁县| 荃湾区| 北宁市| 屏东县| 合川市| 宣威市| 凤阳县| 武城县| 寻甸| 乾安县| 安溪县| 内黄县| 鸡西市| 湾仔区| 舒城县| 新乡市| 剑河县| 农安县| 周宁县| 三台县|