在 Java Web 应用中,想要在接口中获取到发送请求的客户端 IP 地址,需要依赖请求对象 —— HttpServletRequest。

那么,首先就是要先获取到请求的对象。

在 Spring Boot 中,只需在接口方法上加上 HttpServletRequest 或 HttpServletResponse 参数,Spring Boot 就会自动绑定这两个对象,然后可以直接使用。如果你的方法有其他参数,只需把这两个加到后面即可。例如:

@GetMapping("/getSomething")
public String getSomething(..., HttpServletRequest request, HttpServletResponse response) {
//...
}

这是最常用的一种方法,当然除了这种方式,在 Spring Boot 中还可以使用注解 @Autowired 注入或通过 RequestContextHolder 来获取请求对象,这里就不展开叙述了。

在得到请求对象 HttpServletRequest 对象后,一般的,我们可以使用 request.getRemoteAddr() 来获取到客户端对应的 IP 地址。

但是,如果使用了 Nginx 等反向代理软件,则不能只简单地通过上述代码来获取 IP;并且,如果使用了多级反向代理的话,那么在获取 X-Forwarded-For 的值时,得到的应是一串 IP 地址,此种情况下,X-Forwarded-For 中第一个非 unknown 的有效 IP 字符串,则为真实 IP 地址。

获取IP的工具类

public class IpUtil {

public static String getIpAddr(HttpServletRequest request) {
String ipAddress = null;
try {
ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (ipAddress.equals("127.0.0.1")) {
// 根据网卡取本机配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
if (inet != null) {
ipAddress = inet.getHostAddress();
}
}
}
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
} catch (Exception e) {
ipAddress = "";
}
return ipAddress;
}
}

字段解释

X-Forwarded-For

格式为 X-Forwarded-For:client1,proxy1,proxy2,一般情况下,第一个 ip 为客户端真实 ip,后面的为经过的代理服务器 ip。现在大部分的代理都会加上这个请求头。

Proxy-Client-IP/WL- Proxy-Client-IP

这个一般是经过 apache http 服务器的请求才会有,用 apache http 做代理时一般会加上 Proxy-Client-IP 请求头,而 WL-Proxy-Client-IP 是他的 weblogic 插件加上的请求头。

需要注意几点:

  • 这些请求头都不是 http 协议里的标准请求头,也就是说这是各个代理服务器自己规定的表示客户端地址的请求头。如果哪天有一个代理服务器软件用 xxx-client-ip 这个请求头代表客户端请求,那上面的代码就不行了。
  • 这些请求头不是代理服务器一定会带上的,网络上的很多匿名代理就没有这些请求头,所以获取到的客户端 ip 不一定是真实的客户端 ip。代理服务器一般都可以自定义请求头设置。
  • 即使请求经过的代理都会按自己的规范附上代理请求头,上面的代码也不能确保获得的一定是客户端 ip。不同的网络架构,判断请求头的顺序是不一样的。
  • 最重要的一点,请求头都是可以伪造的。如果一些对客户端校验较严格的应用(比如投票)要获取客户端 ip,应该直接使用 request.getRemoteAddr(),虽然获取到的可能是代理的 ip 而不是客户端的 ip,但这个获取到的 ip 基本上是不可能伪造的,也就杜绝了刷票的可能。

如何使用

@Autowired
private HttpServletRequest servletRequest;

String ip = IpUtil.getIpAddr(servletRequest);

Nginx配置

在配置里加上:

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

image-20240519201910916

参考链接

Java 实战系列·获取请求 IP 地址-腾讯云开发者社区-腾讯云 (tencent.com)

通过 HttpServletRequest 获取客户端 IP 地址 - CoderGeshu - 博客园 (cnblogs.com)