通过 Request 请求获取真实 IP 地址以及对应省份城市
创始人
2024-04-28 04:23:30

title: 通过 Request 请求获取真实 IP 地址以及对应省份城市和系统浏览器信息
date: 2022-12-16 16:20:26
tags:

  • GeoIP2
  • UserAgentUtils
    categories:
  • 开发实践
    cover: https://cover.png
    feature: false

1. 获取真实 IP 地址

1.1 代码

代码如下,这里的 CommonUtil.isBlank() 为封装的判空方法

public static String getIpAddress(HttpServletRequest request) {// 首先, 获取 X-Forwarded-For 中的 IP 地址,它在 HTTP 扩展协议中能表示真实的客户端 IPString ipAddress = request.getHeader("X-Forwarded-For");if (CommonUtil.isNotBlank(ipAddress) && !"unknown".equalsIgnoreCase(ipAddress)) {// 多次反向代理后会有多个 ip 值,第一个 ip 才是真实 ip, 例: X-Forwarded-For: client, proxy1, proxy2,proxy…int index = ipAddress.indexOf(",");if (index != -1) {return ipAddress.substring(0, index);}return ipAddress;}// 如果 X-Forwarded-For 获取不到, 就去获取 X-Real-IPipAddress = request.getHeader("X-Real-IP");if (CommonUtil.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {// 如果 X-Real-IP 获取不到, 就去获取 Proxy-Client-IPipAddress = request.getHeader("Proxy-Client-IP");}if (CommonUtil.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {// 如果 Proxy-Client-IP 获取不到, 就去获取 WL-Proxy-Client-IPipAddress = request.getHeader("WL-Proxy-Client-IP");}if (CommonUtil.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {// 如果 WL-Proxy-Client-IP 获取不到, 就去获取 HTTP_CLIENT_IPipAddress = request.getHeader("HTTP_CLIENT_IP");}if (CommonUtil.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {// 如果 HTTP_CLIENT_IP 获取不到, 就去获取 HTTP_X_FORWARDED_FORipAddress = request.getHeader("HTTP_X_FORWARDED_FOR");}if (CommonUtil.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {// 都获取不到, 最后才通过 request.getRemoteAddr() 获取IPipAddress = request.getRemoteAddr();}return "0:0:0:0:0:0:0:1".equals(ipAddress) ? "127.0.0.1" : ipAddress;;
}

1.2 解释

1、首先,获取 X-Forwarded-For 中第 0 位的 IP 地址,它在 HTTP 扩展协议中能表示真实的客户端 IP,如下例:

X-Forwarded-For: client, proxy1, proxy2, proxy…

2、如果 X-Forwarded-For 获取不到,就去获取 X-Real-IPX-Real-IP 获取不到,就依次获取 Proxy-Client-IPWL-Proxy-Client-IPHTTP_CLIENT_IPHTTP_X_FORWARDED_FOR 。最后获取不到才通过 request.getRemoteAddr() 获取 IP

  1. X-Real-IP 记录请求的客户端真实 IP,与 X-Forwarded-For 类似
  2. Proxy-Client-IP 代理客户端的 IP,如果客户端真实 IP 获取不到的时候,就只能获取代理客户端的 IP 了
  3. WL-Proxy-Client-IP 在 Weblogic 下获取真实 IP 所用的的参数
  4. HTTP_CLIENT_IPHTTP_X_FORWARDED_FOR 可以理解为 X-Forwarded-For , 它们是 PHP 中的用法

3、在服务器上通过 request.getRemoteAddr() 获取服务器的地址时,获取到的是 IPV6 的 0:0:0:0:0:0:0:1,需要转换为 IPV4 的 127.0.0.1

1.3 Nginx 配置请求头参数

server {listen       8081;server_name  localhost;location / {root   html/resource-nav;index  index.html index.htm;}location ~ /resNav {#代理请求头相关proxy_set_header Host $host:$server_port; proxy_set_header X-Real-Ip $remote_addr;proxy_set_header X-Forwarded-For $remote_addr;proxy_pass http://ip:port;}
}

2. 通过 IP 地址获取省份城市信息

分为两种方式,在线和离线:

1、使用在线第三方提供的 api:

  • ip-api.com
  • ip.taotao.com
  • 百度地图 api
  • 新浪 iplookup

2、使用离线查询方式:

  • 纯真库
  • GeoLite2
  • 埃文科技

具体的数据丰富性、准确性和查询速度可自行搜集相关资料。由于 GeoLite2 免费,且离线查询速度更快和稳定,同时不限制 API 并发数等,这里使用 GeoLite2 来获取省份城市信息,同时数据丰富性也比较高

2.1 下载 GeoLite2 City 库

GeoLite 数据库是 MaxMind 公司旗下的 ,GeoLite 数据库有开源版本和收费版本,这里使用开源版本,GeoLite 目前已经更新到 2 了,所以下载 GeoLite2 City 库。下载地址如下:GeoLite2 Free Geolocation Data | MaxMind Developer Portal

点击页面中的 Download Files

在这里插入图片描述

未登录的话会跳转到登录页面

在这里插入图片描述

没有账户的话点击创建

在这里插入图片描述

这里会有几种账户形式,选择登录免费的 GeoLite2 数据库和 Web 服务

在这里插入图片描述

填写完对应的信息后,会发一封设置密码的邮件,点击链接设置密码

在这里插入图片描述

完成后点击进行登录

在这里插入图片描述

输入用户名密码进行登录,用户名就是邮箱地址

在这里插入图片描述

选择下载数据库

在这里插入图片描述

选择 GZIP 下载

在这里插入图片描述

下载完成后会得到一个 tar 包文件

在这里插入图片描述

解压后里面就是我们需要的数据库文件(Windows 可用 7-Zip 解压)

在这里插入图片描述

2.2 使用

2.2.1将文件放入项目根路径下

在这里插入图片描述

2.2.2 引入依赖

好像 3.0 版本以上最低支持 JDK 11,假如是 JDK 8 的话最高使用 2.16.1 即可


com.maxmind.geoip2geoip22.16.1

2.2.3 代码

这里的 new DatabaseReader.Builder(database).build() 支持两种类型,一种是 File,一种是 InputStream。本地项目两种皆可,但打包到服务器上运行时获取 File 类型文件可能会存在问题,最好通过流的方式来获取构建

public class Test {public static void main(String[] args) throws IOException, GeoIp2Exception {// 读取数据库文件ClassPathResource classPathResource = new ClassPathResource("GeoLite2-City.mmdb");InputStream database = database = classPathResource.getInputStream();// 创建数据库DatabaseReader reader = new DatabaseReader.Builder(database).build();// 获取 IP 地址信息InetAddress ipAddress = InetAddress.getByName("139.227.47.35");// 获取查询信息CityResponse response = reader.city(ipAddress);// 国家信息Country country = response.getCountry();System.out.println(country.getIsoCode()); // 'CN'System.out.println(country.getName()); // 'China'// {de=China, ru=Китай, pt-BR=China, ja=中国, en=China, fr=Chine, zh-CN=中国, es=China}System.out.println(country.getNames());System.out.println(country.getNames().get("zh-CN")); // '中国'// 省级信息Subdivision subdivision = response.getMostSpecificSubdivision();System.out.println(subdivision.getIsoCode()); // 'SH'System.out.println(subdivision.getName()); // 'Shanghai'// {{en=Shanghai, fr=Municipalité de Shanghai, zh-CN=上海, pt-BR=Xangai}}System.out.println(subdivision.getNames());System.out.println(subdivision.getNames().get("zh-CN")); // '上海'// 城市信息City city = response.getCity();System.out.println(city.getName()); // 'Shanghai'System.out.println(city.getNames().get("zh-CN")); // '上海'// 邮政编码(国内的可能获取不到)Postal postal = response.getPostal();System.out.println(postal.getCode()); // '55423'// 经纬度Location location = response.getLocation();System.out.println(location.getLatitude()); // 纬度 31.2222System.out.println(location.getLongitude()); // 经度 121.4581}
}

在这里插入图片描述

2.3 封装成工具类

1、实体类

@Data
@TableName("login_geo")
public class LoginGeoDO {// 主键IDprivate String id;// 国家 ISO 代码private String countryIsoCode;// 国家名称private String countryName;// 国家中文名称private String countryZhCnName;// 省级 ISO 代码, 外国则是同级别地区代码private String subdivisionIsoCode;// 省级名称private String subdivisionName;// 省级中文名称private String subdivisionZhCnName;// 城市名称private String cityName;// 城市中文名称private String cityZhCnName;// 邮政编码private String postal;// 纬度private double latitude;// 经度private double longitude;// 创建时间private Timestamp createTime;// 更新时间private Timestamp updateTime;
}

2、封装工具类

这里把前面获取 IP 地址的方法也封装进来了,LogUtil 为封装的日志工具类

public class AuthUtil {private static InputStream database;private static DatabaseReader reader;static {// 读取数据库文件LogUtil.info("读取数据库文件");ClassPathResource classPathResource = new ClassPathResource("GeoLite2-City.mmdb");// 创建数据库try {database = classPathResource.getInputStream();reader = new DatabaseReader.Builder(database).build();} catch (IOException e) {throw new RuntimeException(e.getMessage());}}/*** 获取 IP 地址** @param request 请求* @return {@link String}* @author Fan* @since 2022/11/28 9:08*/public static String getIpAddress(HttpServletRequest request) {// 首先, 获取 X-Forwarded-For 中的 IP 地址,它在 HTTP 扩展协议中能表示真实的客户端 IPString ipAddress = request.getHeader("X-Forwarded-For");if (CommonUtil.isNotBlank(ipAddress) && !"unknown".equalsIgnoreCase(ipAddress)) {// 多次反向代理后会有多个 ip 值,第一个 ip 才是真实 ip, 例: X-Forwarded-For: client, proxy1, proxy2,proxy…int index = ipAddress.indexOf(",");if (index != -1) {return ipAddress.substring(0, index);}return ipAddress;}// 如果 X-Forwarded-For 获取不到, 就去获取 X-Real-IPipAddress = request.getHeader("X-Real-IP");if (CommonUtil.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {// 如果 X-Real-IP 获取不到, 就去获取 Proxy-Client-IPipAddress = request.getHeader("Proxy-Client-IP");}if (CommonUtil.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {// 如果 Proxy-Client-IP 获取不到, 就去获取 WL-Proxy-Client-IPipAddress = request.getHeader("WL-Proxy-Client-IP");}if (CommonUtil.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {// 如果 WL-Proxy-Client-IP 获取不到, 就去获取 HTTP_CLIENT_IPipAddress = request.getHeader("HTTP_CLIENT_IP");}if (CommonUtil.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {// 如果 HTTP_CLIENT_IP 获取不到, 就去获取 HTTP_X_FORWARDED_FORipAddress = request.getHeader("HTTP_X_FORWARDED_FOR");}if (CommonUtil.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {// 都获取不到, 最后才通过 request.getRemoteAddr() 获取IPipAddress = request.getRemoteAddr();}return ipAddress;}/*** 通过 IP 地址获取地理信息** @param ipAddress IP地址* @return {@link LoginGeoDO}* @author Fan* @since 2022/12/14 16:35*/public static LoginGeoDO getGeoInformation(String ipAddress) {try {// 获取 IP 地址信息InetAddress inetAddress = InetAddress.getByName(ipAddress);// 获取查询信息CityResponse response = reader.city(inetAddress);LoginGeoDO loginGeoDO = new LoginGeoDO();// 国家信息Country country = response.getCountry();loginGeoDO.setCountryIsoCode(country.getIsoCode());loginGeoDO.setCountryName(country.getName());loginGeoDO.setCountryZhCnName(country.getNames().get("zh-CN"));// 省级信息Subdivision subdivision = response.getMostSpecificSubdivision();loginGeoDO.setSubdivisionIsoCode(subdivision.getIsoCode());loginGeoDO.setSubdivisionName(subdivision.getName());loginGeoDO.setSubdivisionZhCnName(subdivision.getNames().get("zh-CN"));// 城市信息City city = response.getCity();loginGeoDO.setCityName(city.getName());loginGeoDO.setCityZhCnName(city.getNames().get("zh-CN"));// 邮政编码(国内的可能获取不到)Postal postal = response.getPostal();loginGeoDO.setPostal(postal.getCode());// 经纬度Location location = response.getLocation();loginGeoDO.setLatitude(location.getLatitude());loginGeoDO.setLongitude(location.getLongitude());return loginGeoDO;} catch (IOException | GeoIp2Exception exception) {LogUtil.error(exception.getMessage());return null;}}
}

3. 获取系统、浏览器信息

该类信息一般通过 UA(User Agent)标识来获取。 User Agent 中文名为用户代理,简称 UA,它是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等

先获取请求头中的 User-Agent

String ua = request.getHeader("User-Agent");

引入 UserAgentUtils 依赖


eu.bitwalkerUserAgentUtils1.21

使用提供的 UserAgent 类来解析 ua 字符串

UserAgent userAgent = UserAgent.parseUserAgentString(ua);// 操作系统
userAgent.getOperatingSystem().getName()
// 浏览器
userAgent.getBrowser().getName()

相关内容

热门资讯

北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
阿西吧是什么意思 阿西吧相当于... 即使你没有受到过任何外语培训,你也懂四国语言。汉语:你好英语:Shit韩语:阿西吧(아,씨발! )日...