在进行一些小游戏开发时,我们经常比较关注的一个功能便是分享。针对分享,我们希望能根据各个城市或者地区,能有不同的分享文案,辨识地区的功能如果由服务器来完成的话,我们就需要知道客户端的真实 IP。今天我们就来看看服务器是如何获取到客户端的真实 IP 的。
<!– more –>
nginx 配置
首先,一个请求肯定是可以分为请求头和请求体的,而我们客户端的 IP 地址信息一般都是存储在请求头里的。如果你的服务器有用 Nginx 做负载均衡的话,你需要在你的 location 里面配置 X-Real-IP
和X-Forwarded-For
请求头:
location ^~ /your-service/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://localhost:60000/your-service/;
}
X-Real-IP
在《实战 nginx》中,有这么一句话:
经过反向代理后,由于在客户端和 web 服务器之间增加了中间层,因此 web 服务器无法直接拿到客户端的 ip,通过 $remote_addr 变量拿到的将是反向代理服务器的 ip 地址。
这句话的意思是说,当你使用了 nginx 反向服务器后,在 web 端使用 request.getRemoteAddr()
(本质上就是获取$remote_addr
),取得的是 nginx 的地址,即$remote_addr
变量中封装的是 nginx 的地址,当然是没法获得用户的真实 ip 的。但是,nginx 是可以获得用户的真实 ip 的,也就是说 nginx 使用 $remote_addr
变量时获得的是用户的真实 ip,如果我们想要在 web 端获得用户的真实 ip,就必须在 nginx 里作一个赋值操作,即我在上面的配置:
proxy_set_header X-Real-IP $remote_addr;
X-Forwarded-For
X-Forwarded-For
变量,这是一个 squid 开发的,用于识别通过 HTTP 代理或负载平衡器原始 IP 一个连接到 Web 服务器的客户机地址的非 rfc 标准,如果有做 X-Forwarded-For
设置的话, 每次经过 proxy 转发都会有记录, 格式就是 client1,proxy1,proxy2
以逗号隔开各个地址,由于它是非 rfc 标准,所以默认是没有的,需要强制添加。在默认情况下经过 proxy 转发的请求,在后端看来远程地址都是 proxy 端的 ip。也就是说在默认情况下我们使用 request.getAttribute("X-Forwarded-For")
获取不到用户的 ip,如果我们想要通过这个变量获得用户的 ip,我们需要自己在 nginx 添加配置:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
意思是增加一个 $proxy_add_x_forwarded_for
到X-Forwarded-For
里去,注意是增加,而不是覆盖,当然由于默认的 X-Forwarded-For
值是空的,所以我们总感觉 X-Forwarded-For
的值就等于 $proxy_add_x_forwarded_for
的值,实际上当你搭建两台 nginx 在不同的 ip 上,并且都使用了这段配置,那你会发现在 web 服务器端通过 request.getAttribute("X-Forwarded-For")
获得的将会是客户端 ip 和第一台 nginx 的 ip。
那么 $proxy_add_x_forwarded_for
又是什么?
$proxy_add_x_forwarded_for
变量包含客户端请求头中的 X-Forwarded-For
与$remote_addr
两部分,他们之间用逗号分开。
举个例子,有一个 web 应用,在它之前通过了两个 nginx 转发,www.linuxidc.com
即用户访问该 web 通过两台 nginx。
在第一台 nginx 中, 使用:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
现在的 $proxy_add_x_forwarded_for
变量的 X-Forwarded-For
部分是空的,所以只有 $remote_addr
,而$remote_addr
的值是用户的 ip,于是赋值以后,X-Forwarded-For
变量的值就是用户的真实的 ip 地址了。
到了第二台 nginx,使用:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
现在的 $proxy_add_x_forwarded_for
变量,X-Forwarded-For
部分包含的是用户的真实 ip,$remote_addr
部分的值是上一台 nginx 的 ip 地址,于是通过这个赋值以后现在的 X-Forwarded-For
的值就变成了“用户的真实 ip,第一台 nginx 的 ip”,这样就清楚了吧。
服务器获取真实 IP
代码为:
public static String getIpAddress(HttpServletRequest request) {String Xip = request.getHeader("X-Real-IP");
String XFor = request.getHeader("X-Forwarded-For");
if (!Strings.isNullOrEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)) {
// 多次反向代理后会有多个 ip 值,第一个 ip 才是真实 ip
int index = XFor.indexOf(",");
if (index != -1) {return XFor.substring(0, index);
} else {return XFor;}
}
XFor = Xip;
if (!Strings.isNullOrEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)) {return XFor;}
if (Strings.nullToEmpty(XFor).trim().isEmpty() || "unknown".equalsIgnoreCase(XFor)) {XFor = request.getHeader("Proxy-Client-IP");
}
if (Strings.nullToEmpty(XFor).trim().isEmpty() || "unknown".equalsIgnoreCase(XFor)) {XFor = request.getHeader("WL-Proxy-Client-IP");
}
if (Strings.nullToEmpty(XFor).trim().isEmpty() || "unknown".equalsIgnoreCase(XFor)) {XFor = request.getHeader("HTTP_CLIENT_IP");
}
if (Strings.nullToEmpty(XFor).trim().isEmpty() || "unknown".equalsIgnoreCase(XFor)) {XFor = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (Strings.nullToEmpty(XFor).trim().isEmpty() || "unknown".equalsIgnoreCase(XFor)) {XFor = request.getRemoteAddr();
}
return XFor;
}
我们来看看各个请求头的含义
X-Real-IP
nginx 代理一般会加上此请求头。
X-FORWARDED-FOR
这是一个 Squid
开发的字段,只有在通过了 HTTP 代理或者负载均衡服务器时才会添加该项。
Proxy-Client-IP 和 WL-Proxy-Client-IP
这个一般是经过 apache http 服务器的请求才会有,用 apache http 做代理时一般会加上 Proxy-Client-IP
请求头,而 WL-Proxy-Client-IP
是它的 weblogic 插件加上的头。
HTTP_CLIENT_IP
有些代理服务器会加上此请求头。在网上搜了一下,有一个说法是:
这是普通的 http header,伪造起来很容易,不要轻易信任用户输入。curl -H 'client-ip: 8.8.8.8' lidian.club/phpinfo.php | grep _SERVER
你就能看到 _SERVER["HTTP_CLIENT_IP"] 了。client-ip 和 client-host 是在 NAPT 还没普及的年代,企业内网假设的 http 透明代理,传给服务器的 header,只有极少数厂家用过,从来不是标准,也从来没成为过事实标准。(大家最熟悉的事实标准就是 x-forwarded-for)后来出现的 web proxy 也没见用过这个 header。TCP/IP Illustrated Vol 3 没有讲过这个 header,网上的传言不可信。可考的最早痕迹出现在 2005 年,日本一部 Perl/CGI 秘籍(9784798010779,270 页)通过 client-ip 与 via 两个 header 屏蔽代理用户访问。
HTTP_X_FORWARDED_FOR
简称 XFF 头,它代表客户端,也就是 HTTP 的请求端真实的 IP,只有在通过了 HTTP 代理 (比如 APACHE 代理) 或者负载均衡服务器时才会添加该项。它不是 RFC 中定义的标准请求头信息,在 squid 缓存代理服务器开发文档中可以找到该项的详细介绍。如果有该条信息, 说明您使用了代理服务器,地址就是后面的数值。可以伪造。标准格式如下:X-Forwarded-For: client1, proxy1, proxy2
总结
以上就是我在处理客户端真实 IP 的方法,如果你有什么意见或者建议,可以在下方留言。
有兴趣的话可以关注我的公众号或者头条号,说不定会有意外的惊喜。