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

 更新时间:2026年04月13日 09:36:46   作者:爱码少年 00fly.online  
在Web开发中,获取客户端的真实IP地址是一个常见需求,本文将分别给出经典写法(基于工具类/条件判断)和Lambda写法(基于函数式接口 + 流式处理)的实现,并进行对比

在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地址内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot整合EasyExcel实现复杂Excel表格的导入导出

    SpringBoot整合EasyExcel实现复杂Excel表格的导入导出

    这篇文章主要为大家详细介绍了SpringBoot如何整合EasyExcel实现复杂Excel表格的导入导出功能,文中的示例代码讲解详细,感兴趣的小伙伴可以参考下
    2023-11-11
  • 使用Spring中的scope配置和@scope注解

    使用Spring中的scope配置和@scope注解

    这篇文章主要介绍了使用Spring中的scope配置和@scope注解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • Java之SpringCloud nocos注册中心讲解

    Java之SpringCloud nocos注册中心讲解

    这篇文章主要介绍了Java之SpringCloud nocos注册中心讲解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • SpringBoot Knife4j在线API文档框架基本使用

    SpringBoot Knife4j在线API文档框架基本使用

    knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,这篇文章主要介绍了SpringBoot中使用Knife4J在线API文档框架,需要的朋友可以参考下
    2022-12-12
  • java  interface 接口的使用好处分析

    java interface 接口的使用好处分析

    这篇文章主要介绍了java interface 接口的使用好处,结合实例形式分析了java interface接口的功能、基本使用方法及多态性的使用优点,需要的朋友可以参考下
    2019-11-11
  • java类中使用Jfreechart的简单实例

    java类中使用Jfreechart的简单实例

    这篇文章介绍了java类中使用Jfreechart的简单实例,有需要的朋友可以参考一下
    2013-08-08
  • Mybatis学习总结之mybatis使用建议

    Mybatis学习总结之mybatis使用建议

    这篇文章主要介绍了Mybatis学习总结之mybatis使用建议的相关资料,非常具有参考借鉴价值,需要的朋友可以参考下
    2016-05-05
  • intellij idea中spring boot properties文件不能自动提示问题解决

    intellij idea中spring boot properties文件不能自动提示问题解决

    这篇文章主要介绍了intellij idea中spring boot properties文件不能自动提示问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-02-02
  • Java中BigDecimal类与int、Integer使用总结

    Java中BigDecimal类与int、Integer使用总结

    这篇文章主要给大家介绍了关于Java中BigDecimal类与int、Integer使用的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Java具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-07-07
  • 带你快速搞定java多线程(5)

    带你快速搞定java多线程(5)

    这篇文章主要介绍了java多线程编程实例,分享了几则多线程的实例代码,具有一定参考价值,加深多线程编程的理解还是很有帮助的,需要的朋友可以参考下
    2021-07-07

最新评论