先说下各个常用请求头.
X-Real-IP 是Nginx的反向代理标志(只包含真实ip)
x-forwarded-for 是Nginx的反向代理标志(包含真实ip和反向代理服务器地址,以“,”隔开,第一个为用户真实ip,后面的是各个层代理服务器ip)
Proxy-Client-IP 是Apache的反向代理标志
WL-Proxy-Client-IP 是WebLogic的反向代理标志
下面看下常用的获取ip方法,如下所示:
public static String getIpAddr(HttpServletRequest request) {
String ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")) {
// 根据网卡取本机配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress = inet.getHostAddress();
}
}
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
// = 15
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
return ipAddress;
}
上面的方法在测试过程中是能获取到真实ip的,但是前提是用户没有进行伪造。
现在来分析下用户是如何进行伪造请求头。
情况1: 服务器使用的是Apache(Proxy-Client-IP请求头)或者是WebLogic(WL-Proxy-Client-IP请求头)反向代理,或者没有使用反向代理
这种情况下,用户可以伪造x-forwarded-for,由于服务器未使用Nginx反向代理,所以不会构造x-forwarded-for,但是用户却伪造了x-forwarded-for,导致在服务器先执行request.getHeader("x-forwarded-for");,所以获取到的ip是用户伪造的。
情况2:服务器使用的是WebLogic(WL-Proxy-Client-IP请求头)反向代理,或者没有使用反向代理。
用户可以伪造x-forwarded-for或Proxy-Client-IP请求头,此时服务器获取的是用户的伪造ip。
情况3:服务器没有使用反向代理。
用户可以伪造x-forwarded-for或Proxy-Client-IP或WL-Proxy-Client-IP请求头,此时服务器获取的是用户的伪造ip。
情况4:服务器使用的是Nginx(x-forwarded-for 请求头)反向代理。
这种情况下可能大家会人为是没问题的,但如果不配置好Nginx,也会获取到伪造的ip。
比如Nginx的配置是proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
这个配置是对x-forwarded-for请求头进行追加ip。如果用户伪造x-forwarded-for请求头的话,Nginx不会覆盖用户伪造的ip,而是追加到后面,这样获取的也是伪造的ip。
解决方法:
1):未使用反向代理情况下:
未使用反向代理下,不需要从请求头去获取用户ip,用request.getRemoteAddr();方法就能获取到用户的ip了。
2):使用Nginx反向代理情况,代码如下:
public static String getIpAddr(HttpServletRequest request) throws UnknownHostException {
// 从Nginx中X-Real-IP获取真实ip
String ipAddress = request.getHeader("X-Real-IP");
if (ipAddress != null && ipAddress.length() > 0 && !"unknown".equalsIgnoreCase(ipAddress)) {
logger.info("从X-Real-IP中获取到ip:" + ipAddress);
return ipAddress;
}
// 从Nginx中x-forwarded-for获取真实ip
ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress != null && ipAddress.length() > 0 && !"unknown".equalsIgnoreCase(ipAddress)) {
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
int index = ipAddress.indexOf(",");
if (index > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
logger.info("从x-forwarded-for中获取到ip:" + ipAddress);
return ipAddress;
}
ipAddress = request.getRemoteAddr();
if ("127.0.0.1".equals(ipAddress) || "0:0:0:0:0:0:0:1".equals(ipAddress)) {
// 根据网卡取本机配置的IP
ipAddress = InetAddress.getLocalHost().getHostAddress();
}
logger.info("从request.getRemoteAddr()中获取到ip:" + ipAddress);
return ipAddress;
}
以上是获取ip代码,另外Nginx还需要配置正确,如果Nginx反向代理有多台,那么在最外层的反向代理服务器加上
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
内层的反向代理服务器加上
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
(!注意不要加X-Real-IP配置)
配置好后重启Nginx即可
这样java中获取的ip就是真实的ip了(不考虑用户使用代理或者VPN),即使用户伪造请求头,我们获取到的仍然是真实ip。如果Nginx不想配置X-Real-IP,那么也要删除掉java中对应的获取X-Real-IP代码