1. 问题
java程序里面回去http请求的IP地址的方法是:request.getRemoteAddr(),这种方法在很久很久以前是有效的。但是现在的web服务一般都是经过了nginx、Apache,Squid等反向代理软件做了多级代理,这时候获取的就是最后一级代理服务器的ip,而不是发起http请求的客户端的真实IP地址。
例如:如果使用了nginx将客户访问的http://www.abc.com/转向内网真实的web服务http://192.168.1.110:2046/,用request.getRemoteAddr()方法获取的IP地址是:127.0.0.1或192.168.1.110,而并不是客户端的真实IP。
2. 解决办法
网上抄了一段代码,加以修改:
public class RequestUtils {
private static final List<String> PROXY_HEADERS = new ArrayList<>();
static {
PROXY_HEADERS.add("x-forwarded-for");
PROXY_HEADERS.add("Proxy-Client-IP");
PROXY_HEADERS.add("WL-Proxy-Client-IP");
PROXY_HEADERS.add("Http_Client_IP");
PROXY_HEADERS.add("X-Real-IP");
}
public static String getClientIP(HttpServletRequest request) {
try {
if (request == null)
return "";
String ip = "";
for (String proxyHeader : PROXY_HEADERS) {
String ipsInHeader = request.getHeader(proxyHeader);
if ((!StringUtils.isBlank(ipsInHeader)) && (!"unknown".equalsIgnoreCase(ipsInHeader))) {
ip = getFistIPInHeader(ipsInHeader);
break;
}
}
if ("".equals(ip)) {
ip = request.getRemoteAddr();
}
return ip;
} catch (RuntimeException e) {
return "";
}
}
private static String getFistIPInHeader(String ipsInHeader) {
String[] ipGroup = ipsInHeader.split(",");
for (String ip : ipGroup) {
if (!"unknown".equalsIgnoreCase(ip)) {
return ip;
}
}
return "";
}
}
3. 解释
其中用到了四个请求头,这些请求头的意思分别是:
- X-Forwarded-For
获取到的是一个ip列表,格式为:client,proxy1,proxy2。一般情况下,第一个ip为客户端真实ip,后面的为经过的代理服务器ip。现在大部分的代理都会加上这个请求头。 - Proxy-Client-IP/WL- Proxy-Client-IP
没用过,不清楚,网上说这个一般是经过Apache服务器的请求才会有,用Apache做代理时一般会加上Proxy-Client-IP请求头,而WL-Proxy-Client-IP是他的weblogic插件加上的头。 - HTTP_CLIENT_IP
没用过,不清楚,网上说有些代理服务器会加上此请求头。 - X-Real-IP
nginx代理一般会加上此请求头。但是只能获取上一跳的ip,所以需要配置在第一级代理中。并且不能记录各级代理的ip。
比如我们使用nginx做代理,需要在nginx中添加配置:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP #remote_addr;
4. 其他
摘抄一段其他人写的注意事项:
- 这些请求头都不是http协议里的标准请求头,也就是说这个是各个代理服务器自己规定的表示客户端地址的请求头。如果哪天有一个代理服务器软件用oooo-client-ip这个请求头代表客户端请求,那上面的代码就不行了。
- 这些请求头不是代理服务器一定会带上的,网络上的很多匿名代理就没有这些请求头,所以获取到的客户端ip不一定是真实的客户端ip。代理服务器一般都可以自定义请求头设置。
- 即使请求经过的代理都会按自己的规范附上代理请求头,上面的代码也不能确保获得的一定是客户端ip。不同的网络架构,判断请求头的顺序是不一样的。
- 最重要的一点,请求头都是可以伪造的。如果一些对客户端校验较严格的应用(比如投票)要获取客户端ip,应该直接使用ip=request.getRemoteAddr(),虽然获取到的可能是代理的ip而不是客户端的ip,但这个获取到的ip基本上是不可能伪造的,也就杜绝了刷票的可能。(有分析说arp欺骗+syn有可能伪造此ip,如果真的可以,这是所有基于TCP协议都存在的漏洞),这个ip是tcp连接里的ip。