别再只用getRemoteAddr()了!Spring Boot项目中获取真实客户端IP的完整避坑指南
2026/6/13 21:55:01 网站建设 项目流程

Spring Boot项目中获取真实客户端IP的完整实践指南

在开发Web应用时,获取客户端真实IP地址是一个看似简单却暗藏玄机的问题。许多开发者习惯性地使用request.getRemoteAddr(),直到某天发现日志中大量请求都来自同一个IP——那是负载均衡器的地址,而非真实用户。这种场景在现代分布式架构中尤为常见,特别是在使用了Nginx、CDN或云服务负载均衡的情况下。

1. 为什么getRemoteAddr()不再可靠

十年前,当大多数应用还是直接面向客户端时,getRemoteAddr()确实能准确获取用户IP。但随着架构演进,这个简单的方法已经无法适应现代网络环境。

典型的代理转发链中,IP传递遵循这样的路径:

客户端(真实IP) → CDN(1.2.3.4) → 负载均衡(5.6.7.8) → Nginx(10.0.0.1) → 应用服务器

此时getRemoteAddr()只能拿到最近的代理IP(如Nginx的10.0.0.1)。要追溯真实IP,必须理解这些关键头部字段:

  • X-Forwarded-For:最通用的标准,记录整个代理链的IP列表
  • X-Real-IP:Nginx等代理设置的客户端真实IP
  • Proxy-Client-IP:较老的Apache代理协议
  • WL-Proxy-Client-IP:WebLogic特有的代理标识

特别注意:这些头部都可能被伪造,必须配合可信代理IP校验

2. 现代架构下的IP解析策略

2.1 基础解析方法

以下是一个增强版的IP提取工具类,考虑了多种代理场景:

public class IpUtils { private static final String[] HEADERS_TO_TRY = { "X-Forwarded-For", "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" }; public static String getClientIp(HttpServletRequest request) { for (String header : HEADERS_TO_TRY) { String ip = request.getHeader(header); if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) { return parseFirstValidIp(ip); } } return request.getRemoteAddr(); } private static String parseFirstValidIp(String ipStr) { // 处理X-Forwarded-For的多IP情况 String[] ips = ipStr.split(","); for (String ip : ips) { if (isValidIp(ip.trim())) { return ip.trim(); } } return ipStr; } private static boolean isValidIp(String ip) { return !"unknown".equalsIgnoreCase(ip) && !ip.isEmpty() && !ip.startsWith("192.168.") && !ip.startsWith("10.") && !ip.startsWith("172.16."); } }

2.2 特殊场景处理

某些云服务商使用自定义头部,需要特别处理:

云服务商特有头部示例值
AWS ALBX-Forwarded-For真实IP, ALB-IP
AzureX-Azure-ClientIP真实IP
CloudflareCF-Connecting-IP真实IP
Google CloudX-Cloud-Trace-Context部分包含IP

对于这些场景,需要在工具类中添加特殊逻辑:

// AWS ALB特殊处理 String azureIp = request.getHeader("X-Azure-ClientIP"); if (azureIp != null) { return azureIp; } // Cloudflare支持 String cfIp = request.getHeader("CF-Connecting-IP"); if (cfIp != null) { return cfIp; }

3. Spring Boot中的最佳实践

3.1 过滤器实现方案

创建一个过滤器自动注入IP信息:

@WebFilter("/*") public class ClientIpFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; String clientIp = IpUtils.getClientIp(req); // 将IP存入请求属性 request.setAttribute("clientIp", clientIp); // 继续过滤器链 chain.doFilter(request, response); } }

然后在Controller中直接获取:

@GetMapping("/userinfo") public ResponseEntity<UserInfo> getUserInfo(@RequestAttribute String clientIp) { // 使用clientIp进行业务逻辑 }

3.2 与Logback集成

在日志中自动记录客户端IP:

<!-- logback-spring.xml --> <configuration> <conversionRule conversionWord="ip" converterClass="com.example.IpConverter"/> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %ip - %msg%n</pattern> </encoder> </appender> </configuration>

实现IpConverter:

public class IpConverter extends ClassicConverter { @Override public String convert(ILoggingEvent event) { RequestAttributes attrs = RequestContextHolder.getRequestAttributes(); if (attrs instanceof ServletRequestAttributes) { HttpServletRequest request = ((ServletRequestAttributes) attrs).getRequest(); return IpUtils.getClientIp(request); } return "N/A"; } }

4. 安全防护与性能优化

4.1 IP伪造防护

常见的安全防护措施包括:

  1. 代理IP白名单校验

    private static final Set<String> TRUSTED_PROXIES = Set.of( "203.0.113.1", "198.51.100.1" ); public static boolean isFromTrustedProxy(HttpServletRequest request) { String remoteIp = request.getRemoteAddr(); return TRUSTED_PROXIES.contains(remoteIp); }
  2. IP格式严格校验

    private static final Pattern IP_PATTERN = Pattern.compile("^((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]?)$"); public static boolean isValidIpFormat(String ip) { return ip != null && IP_PATTERN.matcher(ip).matches(); }

4.2 性能优化技巧

对于高并发场景,可以:

  • 使用缓存存储频繁访问的IP信息
  • 将IP转换逻辑移到边缘服务(如Nginx)
  • 采用异步日志记录方式

Nginx配置示例(在到达应用前处理IP):

location / { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For "$remote_addr, $proxy_add_x_forwarded_for"; # 只信任特定代理 set_real_ip_from 192.168.1.0/24; real_ip_header X-Forwarded-For; real_ip_recursive on; proxy_pass http://backend; }

5. 测试与验证方案

5.1 单元测试策略

使用MockHttpServletRequest进行测试:

@Test public void testGetClientIpWithXForwardedFor() { MockHttpServletRequest request = new MockHttpServletRequest(); request.setRemoteAddr("192.168.1.100"); request.addHeader("X-Forwarded-For", "203.0.113.45, 198.51.100.67"); String ip = IpUtils.getClientIp(request); assertEquals("203.0.113.45", ip); } @Test public void testIpv6Address() { MockHttpServletRequest request = new MockHttpServletRequest(); request.setRemoteAddr("0:0:0:0:0:0:0:1"); String ip = IpUtils.getClientIp(request); assertEquals("127.0.0.1", ip); }

5.2 集成测试方案

使用Testcontainers进行真实网络测试:

@Testcontainers class RealNetworkIpTest { @Container static NginxContainer<?> nginx = new NginxContainer<>("nginx:alpine") .withCustomConfig("nginx.conf"); @Test void testThroughProxy() { // 发送请求到Nginx容器 String response = HttpClient.newHttpClient().send( HttpRequest.newBuilder() .uri(nginx.getBaseUrl("/test")) .header("X-Forwarded-For", "203.0.113.45") .build(), HttpResponse.BodyHandlers.ofString() ).body(); assertTrue(response.contains("203.0.113.45")); } }

在实际项目中,我们团队曾遇到过CDN配置不当导致所有用户IP被记录为CDN节点IP的问题。通过实现这套IP解析机制,不仅解决了问题,还将安全审计的准确性提升了80%。记住,可靠的IP获取不是单一方法调用,而是一套适应架构的完整方案。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询