当前位置: 代码网 > it编程>编程语言>Java > SpringBoot中获取真实客户端IP的终极方案

SpringBoot中获取真实客户端IP的终极方案

2026年01月07日 Java 我要评论
引言:为什么你的ip获取方式可能是错的?在日常开发中,获取客户端ip看似简单,实则暗藏玄机。很多开发者直接使用request.getremoteaddr(),结果在生产环境中发现获取到的都是负载均衡器

引言:为什么你的ip获取方式可能是错的?

在日常开发中,获取客户端ip看似简单,实则暗藏玄机。很多开发者直接使用request.getremoteaddr(),结果在生产环境中发现获取到的都是负载均衡器的ip,而非真实用户ip。更糟糕的是,有些方案存在安全漏洞,可能被恶意用户伪造ip地址。

今天,我将彻底揭秘spring boot中获取真实客户端ip的正确姿势,让你避开所有坑!

一、理解ip传递的底层原理

1.1 为什么需要特殊处理

在现代web架构中,请求往往要经过多个中间件:

用户 → cdn → 负载均衡器 → 网关 → 应用服务器

每个环节都会修改请求信息,导致简单的getremoteaddr()失效。

1.2 关键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的关键,但很多人用错了!

x-forwarded-for: 客户端真实ip, 代理服务器1ip, 代理服务器2ip, ...

重要规则:

  • 最左边的ip是原始客户端ip
  • 后续ip是经过的代理服务器ip
  • 多个ip用逗号分隔

实际场景示例:

// 场景1:直接访问(无代理)
x-forwarded-for: null

// 场景2:经过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工具类

下面这个工具类经过生产环境千锤百炼,直接复制使用即可!

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 = ",";
    
    // 内网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. 优先检查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);
        
        // 从右向左查找第一个公网ip(更安全)
        for (int i = ips.length - 1; i >= 0; i--) {
            string ip = ips[i].trim();
            if (isvalidip(ip) && !isinternalip(ip)) {
                return ip;
            }
        }
        
        // 如果没有公网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);
    }
    
    /**
     * 验证是否为公网ip
     */
    private static boolean isvalidpublicip(string ip) {
        return isvalidip(ip) && !isinternalip(ip) && !islocalhost(ip);
    }
    
    /**
     * 检查是否为内网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");
            // 信任的内网代理(根据实际情况调整)
            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黑名单(可从数据库或配置中心加载)
    private final set<string> blacklistedips = concurrenthashmap.newkeyset();
    
    // ip访问频率限制(简单的内存实现,生产环境建议用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();
        
        // 限制规则:每分钟最多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"));
    }
    
    // 频率限制内部类
    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;
        }
    }
}

五、实战测试:验证你的ip获取是否正确

5.1 调试控制器

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

六、生产环境最佳实践

6.1 配置管理

  • 将信任的代理ip列表配置在配置中心,支持动态更新
  • 为不同环境(开发、测试、生产)设置不同的代理配置

6.2 监控告警

@component
public class ipmonitor {
    
    @eventlistener
    public void handleblacklistevent(blacklistevent event) {
        // 发送告警通知
        alertservice.sendalert("ip黑名单新增: " + event.getip());
    }
    
    @scheduled(fixedrate = 300000) // 5分钟执行一次
    public void cleanupratelimit() {
        // 定期清理过期的频率限制记录
    }
}

6.3 性能优化

  • 对于高并发场景,使用redis实现分布式频率限制
  • 对ip查询结果进行适当缓存(注意缓存时间不宜过长)

七、常见问题排查

q1: 为什么获取到的还是127.0.0.1?

a: 检查负载均衡器是否正确配置了x-forwarded-for头。

q2: 多级代理下如何确定真实ip?

a: 使用本文提供的parsexforwardedfor方法,它会自动处理多级代理情况。

q3: 如何防止ip伪造?

a: 配置internal-proxies只信任内部代理服务器,不信任客户端传递的头部。

总结

获取真实客户端ip是web开发中的基础但重要的工作。通过本文的完整方案,你可以:

  • 正确获取多级代理后的真实客户端ip
  • 有效防止ip地址伪造攻击
  • 实现ip级别的安全防护
  • 具备完整的监控和调试能力

记住:不要相信客户端传递的任何信息,始终通过可信的代理服务器头字段来获取真实ip。

以上就是springboot中获取真实客户端ip的终极方案的详细内容,更多关于springboot获取客户端ip的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com