第一步:如何拿到用户的真实ip
大家都知道,我们一般想访问公网,一般必须具备上网环境,那么我们开通宽带之后,运营商会给我们分配一个ip地址。一般ip地址我们都是自动分配的。所以我们不知道本机地址是什么?想知道自己的ip公网地址,可以通过百度搜索ip查看自己的ip位置
那么问题来了。百度是怎么知道我的公网ip的?
一般情况,用户访问我们的服务网络拓扑如下:
用户通过域名或者ip访问门户,然后请求到后端服务。这样的话后端服务就可以通过request.getremoteaddr();方法获取用户的ip。
springboot获取ip如下:
@restcontroller public class ipcontroller { @requestmapping("/getip") public string hello(httpservletrequest request) { string ip = request.getremoteaddr(); system.out.println(ip); return ip; } }
将服务部署到服务端,然后请求该接口,即可获取ip信息,如下图:
但是为什么我们获取的ip和百度搜出来的不一样呢?
1.1内网ip和外网ip
打开电脑cmd,输出ipconfig命令,查看本机的ip地址,发现我们本机地址和程序获取的地址是一样的。
其实,网络也是分内网ip和公网ip的。内网也成局域网。对于像公司,学校这种一般内部建立自己的局域网,对内部的信息进行传输时,都是通过内网相互通讯,建立局域网内网通讯节省了公网ip资源,并且通信效率也有很大的提升。当然非局域网内的设备则无法向内网的设备发送信息。
但是机器想要访问互联网的资源时,则需要机器拥有外网带宽,也就是我们所说的分配公网ip,负责也是无法访问互联网资源的。
因此,我们把服务部署在同一局域网内,客户端使用内网进行通信,因此获取的就是内网ip地址。但访问百度是需要使用公网访问,因此百度搜出来的ip就是公网ip地址。
1.2.为什么有时候获取到的客户端ip有问题?
当我们兴致勃勃的把ip获取的功能搞上去之后,发现获取的ip都是同一个?这是为什么呢?不可能只是一个用户在访问呀?查询ip信息之后发现,原来是我们部署的一台负载均衡的ip地址。
那么后端服务获取的地址都是负载均衡如nginx的地址。那么怎么透过负载均衡获取真实的地址呢?
透明的代理服务器在将客户端的访问请求转发到下一环节的服务器时,会在http的请求头中添加一条x-forwarded-for记录,用于记录客户端的ip,格式为x-forwarded-for:客户端ip。如果客户端和服务器之间有多个代理服务器,则x-forwarded-for记录使用以下格式记录客户端ip和依次经过的代理服务器ip:x-forwarded-for:客户端ip, 代理服务器1的ip, 代理服务器2的ip, 代理服务器3的ip, ……。
因此,常见的web应用服务器可以通过解析x-forwarded-for记录获取客户端真实ip。
public static string getip(httpservletrequest request) { string ip = request.getheader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsignorecase(ip)) { ip = request.getremoteaddr(); } else if (ip.length() > 15) { //多次反向代理后会有多个ip值,第一个ip才是真实ip string[] ips = ip.split(","); for (int index = 0; index < ips.length; index++) { string strip = ips[index]; ip = strip; break; } } return ip; }
第二步:如何解析ip
ip来了,我们怎么解析呢:
ip的解析一般都要借助第三方软件使用了,第三方一般也分为离线库和在线库
- 离线库支持的有如:ipip,使用离线库的好处是解析效率高,性能好,问题就是ip库要经常更新。如果大家需要我私信我可以提供给大家比较新版本的ip库。
- 在线库则各大云厂商接口能力都有支持。在线版本的好处是更新即时,问题就是接口查询性能和使用tps有要求。
以下演示借助ip库离线ip解析方式:
借助ip库就可以帮我们实现ip地址的解析。
public static void main(string[] args) { ipaddrinfo ipaddrinfo = ipaddr.getinstance().putlocinfo("114.103.71.226"); system.out.println(jsonobject.tojsonstring(ipaddrinfo)); } public ipaddrinfo putlocinfo(string ip) { ipaddrinfo info = new ipaddrinfo(); if (stringutils.isnotblank(ip)) { try { districtinfo addrinfo = db.findinfo(ip, "cn"); info.setcity(addrinfo.getcityname()); info.setcountry(addrinfo.getcountryname()); info.setcountrycode(addrinfo.getchinaadmincode()); info.setisp(addrinfo.getisp()); info.setlat(addrinfo.getlatitude()); info.setlon(addrinfo.getlongitude()); info.setprovince(addrinfo.getregionname()); info.settimezone(addrinfo.gettimezone()); system.out.println(addrinfo.tostring()); } catch (ipformatexception e) { e.printstacktrace(); } catch (invaliddatabaseexception e) { e.printstacktrace(); } } return info; }
其实ip的定位解析其实就是一个巨大的位置库,同时ip数量也是有限制的,因此同一个ip也可能会分配到不同的区域,因此影响ip解析位置准确率的有几个方面
1、位置库不精准,导致解析偏差大或者地区字段确实
2、离线库更新不及时 并且海外的一般有专门的离线库去支持,使用同一套离线库并不一定支持海外ip的解析,所以本次受影响最大的海外网红门被解析到中国各个地区,被大家认为造假,当然也包括真的有造假。
不过上线了这个功能也是有好处的,至少网络不是法外之地,大家也要有序的健康的冲浪,拒绝网络暴力。