当前位置: 代码网 > it编程>编程语言>Java > Java获取客户端真实IP地址经典写法详解

Java获取客户端真实IP地址经典写法详解

2026年04月13日 Java 我要评论
在web开发中,获取客户端的真实ip地址是一个常见需求。由于客户端可能经过代理、负载均衡或cdn,request.getremoteaddr()往往只能拿到代理服务器的ip。因此,需要从特定的http

在web开发中,获取客户端的真实ip地址是一个常见需求。由于客户端可能经过代理、负载均衡或cdn,request.getremoteaddr() 往往只能拿到代理服务器的ip。因此,需要从特定的http头(如 x-forwarded-forproxy-client-ipwl-proxy-client-ip)中解析出原始ip。

下面分别给出经典写法(基于工具类/条件判断)和lambda写法(基于函数式接口 + 流式处理)的实现,并进行对比。

经典写法(传统工具类)

使用 if-else 链和循环逐层解析,逻辑清晰,易于理解和调试。

import javax.servlet.http.httpservletrequest;
public class iputils {
    /**
     * 获取客户端真实ip地址
     * @param request httpservletrequest
     * @return 真实ip,如果无法获取则返回 "unknown"
     */
    public static string getclientip(httpservletrequest request) {
        string ip = request.getheader("x-forwarded-for");
        if (ip == null || ip.isempty() || "unknown".equalsignorecase(ip)) {
            ip = request.getheader("proxy-client-ip");
        }
        if (ip == null || ip.isempty() || "unknown".equalsignorecase(ip)) {
            ip = request.getheader("wl-proxy-client-ip");
        }
        if (ip == null || ip.isempty() || "unknown".equalsignorecase(ip)) {
            ip = request.getheader("http_client_ip");
        }
        if (ip == null || ip.isempty() || "unknown".equalsignorecase(ip)) {
            ip = request.getheader("http_x_forwarded_for");
        }
        if (ip == null || ip.isempty() || "unknown".equalsignorecase(ip)) {
            ip = request.getremoteaddr();
        }
        // 对于通过多级代理的情况,取第一个非 unknown 的ip
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        return ip;
    }
}

lambda写法(java 8+)

利用 stream 和 optional 对头名称列表进行链式处理,代码更紧凑、函数式风格更明显。

import javax.servlet.http.httpservletrequest;
import java.util.arrays;
import java.util.optional;
public class iputilslambda {
    private static final string[] headers_to_try = {
        "x-forwarded-for",
        "proxy-client-ip",
        "wl-proxy-client-ip",
        "http_client_ip",
        "http_x_forwarded_for"
    };
    /**
     * 获取客户端真实ip地址(lambda风格)
     * @param request httpservletrequest
     * @return 真实ip,默认返回 request.getremoteaddr()
     */
    public static string getclientip(httpservletrequest request) {
        string ip = arrays.stream(headers_to_try)
                .map(request::getheader)
                .filter(header -> header != null && !header.isempty() && !"unknown".equalsignorecase(header))
                .findfirst()
                .orelseget(request::getremoteaddr);
        // 处理多级代理情况
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        return ip;
    }
}

或者更进一步,将分割逻辑也融入流中:

public static string getclientipadvanced(httpservletrequest request) {
    return arrays.stream(headers_to_try)
            .map(request::getheader)
            .filter(header -> header != null && !header.isempty() && !"unknown".equalsignorecase(header))
            .map(header -> header.contains(",") ? header.split(",")[0].trim() : header)
            .findfirst()
            .orelseget(request::getremoteaddr);
}

两种写法对比

对比维度经典写法lambda写法
可读性直观,每个条件分支清晰可见,适合所有java开发者代码紧凑,但对不熟悉函数式编程的开发者有一定阅读门槛
代码行数较多(约20行)较少(约10行)
扩展性添加新头需要增加一个 if 块只需在 headers_to_try 数组中增加一个元素
性能几乎无差异,都是 o(n) 遍历,且只在请求生命周期内执行一次同样 o(n),但 stream 会引入少量额外开销(微乎其微)
调试便利性可在任意 if 处打断点,逐行跟踪流内调试相对困难,需借助 peek() 或 ide 的流调试插件
错误处理可针对每个头单独处理异常或日志需要额外在 map 或 filter 中处理,不够直观
团队接受度高,任何java程序员都能立即理解依赖于团队对函数式编程的熟悉程度

注意事项与最佳实践

  • 代理信任x-forwarded-for 头可以被伪造,如果应用直接暴露在公网,不应完全信任该头。通常需要在反向代理(如nginx)层面配置 proxy_set_header x-forwarded-for $remote_addr,并限制内部网络才能传递该头。
  • ip格式:ipv4和ipv6均可能出现在头中,无需额外处理,直接取字符串即可。
  • 多级代理x-forwarded-for 的值格式为 client, proxy1, proxy2,通常取第一个(最左侧)为真实客户端ip。
  • 空值处理:许多代理会返回 unknown 字符串,需要过滤掉。
  • 性能考虑:获取ip的操作发生在每个请求中,但遍历几个头字符串的性能开销可以忽略不计。
  • spring boot集成:如果使用spring boot,也可以直接使用内置工具类 requestutils.getremoteaddress() 或 servletwebrequest.getheadervalues(),但原理与上述相同。

方法补充

下面对比了两种java获取客户端ip的实现方式。经典写法使用for循环遍历ip头信息数组,逐个检查并返回第一个有效ip;lambda写法则利用stream api,通过链式操作完成相同的逻辑,代码更简洁。两种方法都考虑了多种代理头信息(x-forwarded-for等)和ip格式处理(分割、去空、trim),最终回退到request.getremoteaddr()。lambda写法展现了函数式编程的优势,但两者在功能上完全等效。

经典写法

    @autowired
    httpservletrequest request;
    
    string[] ip_headers = {"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"};
  
    private string getclientipclassic()
    {
        log.info("##### classic");
        for (string header : ip_headers)
        {
            string ip = request.getheader(header);
            if (stringutils.isnotblank(ip) && !stringutils.equalsignorecase("unknown", ip))
            {
                log.info("##### ip header: {}", header);
                string[] ips = ip.split(",");
                return ips[0].trim();
            }
        }
        log.info("##### request.getremoteaddr");
        return request.getremoteaddr();
    }

lambda写法

    private string getclientiplambda()
    {
        log.info("##### lambda");
        return arrays.stream(ip_headers)
            .peek(log::info)
            .map(request::getheader)
            .filter(ips -> stringutils.isnotblank(ips) && !"unknown".equalsignorecase(ips))
            .flatmap(ips -> arrays.stream(ips.split(",")))
            .filter(stringutils::isnotblank)
            .map(string::trim)
            .findfirst()
            .orelse(request.getremoteaddr());
    }

总结

  • 经典写法:适合大多数项目,尤其是维护性优先、团队成员技术水平不一的场景。它直观、易于调试和扩展,没有额外的学习成本。
  • lambda写法:适合追求代码简洁、熟悉函数式编程、且头名称固定不变的场景。它减少了样板代码,让意图更聚焦于“从一系列候选头中找出第一个非空的有效值”。

实际项目中,两种写法均可正确工作。如果项目已经使用 java 8+ 并普遍采用 stream api,lambda写法可以提升代码的表达力;否则,经典写法更加稳妥。

到此这篇关于java获取客户端真实ip地址经典写法详解的文章就介绍到这了,更多相关java获取客户端ip地址内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

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

发表评论

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