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

SpringBoot獲取真實(shí)客戶端IP的正確方法

 更新時(shí)間:2026年03月15日 11:36:37   作者:神奇小湯圓  
在日常開發(fā)中,獲取客戶端IP看似簡單,實(shí)則暗藏玄機(jī),很多開發(fā)者直接使用request.getRemoteAddr(),結(jié)果在生產(chǎn)環(huán)境中發(fā)現(xiàn)獲取到的都是負(fù)載均衡器的IP,而非真實(shí)用戶IP,本文將徹底揭秘Spring Boot中獲取真實(shí)客戶端IP的正確姿勢,需要的朋友可以參考下

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

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

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

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

1.1 為什么需要特殊處理?

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

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

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

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

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

1.3 X-Forwarded-For的深度解析

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

X-Forwarded-For: 客戶端真實(shí)IP, 代理服務(wù)器1IP, 代理服務(wù)器2IP, ...

重要規(guī)則:

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

實(shí)際場景示例:

// 場景1:直接訪問(無代理)
X-Forwarded-For: null
// 場景2:經(jīng)過CDN
X-Forwarded-For: 123.45.67.89
// 場景3:CDN + Nginx負(fù)載均衡  
X-Forwarded-For: 123.45.67.89, 10.0.1.100
// 場景4:復(fù)雜代理鏈
X-Forwarded-For: 123.45.67.89, 203.0.113.195, 198.51.100.10

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

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

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
 * IP工具類 - 獲取真實(shí)客戶端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段(用于識別代理服務(wù)器)
    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."
    ));
    /**
     * 獲取真實(shí)客戶端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);
        // 從右向左查找第一個(gè)公網(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,返回第一個(gè)有效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;
    }
    /**
     * 驗(yàn)證IP是否有效
     */
    private static boolean isValidIp(String ip) {
        return ip != null && 
               !ip.isEmpty() && 
               !UNKNOWN.equalsIgnoreCase(ip) &&
               isValidIpAddress(ip);
    }
    /**
     * 驗(yàn)證是否為公網(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);
    }
    /**
     * 驗(yàn)證IP地址格式
     */
    public static boolean isValidIpAddress(String ip) {
        if (ip == null || ip.isEmpty()) return false;
        // IPv4驗(yàn)證
        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簡化驗(yàn)證
        if (ip.contains(":")) return true;
        return false;
    }
}

三、Spring Boot配置:讓服務(wù)器認(rèn)識代理

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ù)實(shí)際情況調(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 應(yīng)用配置文件

# 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攔截與安全防護(hù)

4.1 IP攔截器(自動(dòng)記錄)

@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)存實(shí)現(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); // 自動(dòng)加入黑名單
            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;
        }
    }
}

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

5.1 調(diào)試控制器

java

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)境最佳實(shí)踐

6.1 配置管理

  • 將信任的代理IP列表配置在配置中心,支持動(dòng)態(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實(shí)現(xiàn)分布式頻率限制
  • 對IP查詢結(jié)果進(jìn)行適當(dāng)緩存(注意緩存時(shí)間不宜過長)

七、常見問題排查

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

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

Q2: 多級代理下如何確定真實(shí)IP?

A:  使用本文提供的parseXForwardedFor方法,它會(huì)自動(dòng)處理多級代理情況。

Q3: 如何防止IP偽造?

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

總結(jié)

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

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

記?。?strong>不要相信客戶端傳遞的任何信息,始終通過可信的代理服務(wù)器頭字段來獲取真實(shí)IP。

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

相關(guān)文章

  • Java的ConcurrentHashMap原理深入分析

    Java的ConcurrentHashMap原理深入分析

    這篇文章主要介紹了Java的ConcurrentHashMap原理深入分析,集合是編程中最常用的數(shù)據(jù)結(jié)構(gòu),而談到并發(fā),幾乎總是離不開集合這類高級數(shù)據(jù)結(jié)構(gòu)的支持,需要的朋友可以參考下
    2023-07-07
  • Spring框架花式創(chuàng)建Bean的n種方法(小結(jié))

    Spring框架花式創(chuàng)建Bean的n種方法(小結(jié))

    這篇文章主要介紹了Spring框架花式創(chuàng)建Bean的n種方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • Java線程的屬性深入理解

    Java線程的屬性深入理解

    本文介紹了Java線程的幾個(gè)核心屬性,包括線程ID、線程名稱、線程狀態(tài)、優(yōu)先級、守護(hù)線程、是否存活以及異常處理器,通過這些屬性的操作,可以更好地理解和控制線程的行為,感興趣的朋友跟隨小編一起看看吧
    2025-11-11
  • Java instanceof關(guān)鍵字的的進(jìn)一步理解

    Java instanceof關(guān)鍵字的的進(jìn)一步理解

    這篇文章主要介紹了Java instanceof關(guān)鍵字的的進(jìn)一步理解,本文用一些實(shí)例講解了instanceof操作符的一些知識,需要的朋友可以參考下
    2015-03-03
  • Java連接SQL?Server數(shù)據(jù)庫的超詳細(xì)教程

    Java連接SQL?Server數(shù)據(jù)庫的超詳細(xì)教程

    在Java應(yīng)用程序中我們經(jīng)常需要與數(shù)據(jù)庫進(jìn)行交互,一種常見的數(shù)據(jù)庫是Microsoft?SQL?Server,下面這篇文章主要給大家介紹了關(guān)于Java連接SQL?Server數(shù)據(jù)庫的超詳細(xì)教程,需要的朋友可以參考下
    2024-01-01
  • Java多線程開發(fā)工具之CompletableFuture的應(yīng)用詳解

    Java多線程開發(fā)工具之CompletableFuture的應(yīng)用詳解

    做Java編程,難免會(huì)遇到多線程的開發(fā),但是JDK8這個(gè)CompletableFuture類很多開發(fā)者目前還沒聽說過,但是這個(gè)類實(shí)在是太好用了,本文就來聊聊它的應(yīng)用吧
    2023-03-03
  • java生成抽樣隨機(jī)數(shù)的多種算法

    java生成抽樣隨機(jī)數(shù)的多種算法

    本文主要介紹了java生成抽樣隨機(jī)數(shù)的多種算法,主要是基于random庫函數(shù)的,有需要的可以了解一下。
    2016-10-10
  • JAVA實(shí)現(xiàn)經(jīng)典游戲坦克大戰(zhàn)的示例代碼

    JAVA實(shí)現(xiàn)經(jīng)典游戲坦克大戰(zhàn)的示例代碼

    小時(shí)候大家都玩過坦克大戰(zhàn)吧,熟悉的旋律和豐富的關(guān)卡陪伴了我們一整個(gè)寒暑假。本文將通過Java+Swing實(shí)現(xiàn)這一經(jīng)典游戲,感興趣的可以學(xué)習(xí)一下
    2022-01-01
  • java解析json數(shù)組方式

    java解析json數(shù)組方式

    這篇文章主要介紹了java解析json數(shù)組方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • 一文帶你搞懂Java中Synchronized和Lock的原理與使用

    一文帶你搞懂Java中Synchronized和Lock的原理與使用

    這篇文章主要為大家詳細(xì)介紹了Java中Synchronized和Lock的原理與使用,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Java有一定的幫助,需要的可以參考一下
    2023-04-04

最新評論

安国市| 黔西| 武清区| 邯郸市| 朝阳市| 郸城县| 惠东县| 巴南区| 休宁县| 十堰市| 安国市| 乌恰县| 娱乐| 双牌县| 潜山县| 侯马市| 永德县| 亳州市| 东海县| 三台县| 寻甸| 大关县| 平乡县| 舒兰市| 昭觉县| 宁强县| 宜良县| 威信县| 万安县| 桓台县| 郧西县| 察雅县| 子洲县| 阳高县| 黑水县| 菏泽市| 穆棱市| 耿马| 安龙县| 鄂托克旗| 乐安县|