通过 Request 请求获取真实 IP 地址以及对应省份城市

news/2024/11/19 11:19:54/

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 即可

<!-- GeoIP2 -->
<dependency><groupId>com.maxmind.geoip2</groupId><artifactId>geoip2</artifactId><version>2.16.1</version>
</dependency>

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 依赖

<!-- UserAgentUtils -->
<dependency><groupId>eu.bitwalker</groupId><artifactId>UserAgentUtils</artifactId><version>1.21</version>
</dependency>

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

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

http://www.ppmy.cn/news/5573.html

相关文章

六、http模块

HTTP —— 超文本传输协议&#xff0c;用于规范客户端浏览器和服务端以何种格式进行通信和数据交互&#xff1b;HTTP由请求和响应构成的&#xff0c;是一个标准的客服端服务器模型。 HTTP请求响应过程 先简单的来了解以下HTTP的请求响应过程&#xff1a;1.地址解析&#xff1a…

vue实现将自己网站(h5链接)分享到微信中形成小卡片(超详细)

大家好&#xff0c;我是雄雄。 前言 我们在分享公众号信息到微信或者群中的时候&#xff0c;会出现一个小卡片&#xff0c;如下所示&#xff1a; 但是呢&#xff0c;这种小卡片只能走微信的接口来实现&#xff0c;比如我们从公众号、小程序中分享的内容可以是这样的。如果我们…

python接口自动化测试 - mock模块基本使用介绍

mock简介 py3已将mock集成到unittest库中为的就是更好的进行单元测试简单理解&#xff0c;模拟接口返回参数通俗易懂&#xff0c;直接修改接口返回参数的值官方文档&#xff1a;unittest.mock --- 模拟对象库 — Python 3.11.1 文档 mock作用 解决依赖问题&#xff0c;达到解…

5万字企业数字化运营管理平台软件开发框架项目技术方案

目录 1 项目总体概述 项目总体技术方案保障 系统设计 系统体系结构设计 系统指标保障 系统质量 系统健壮性 系统应具备安全性 系统易用性 系统可维护性 系统完备性 系统可扩展性 系统可测试性 系统可移植性 系统可追踪性 系统易安装性 2 项目技术方案 2.1 系统…

git diff 命令6种使用场景

目录 一、背景&目标 二、git三个区了解 三、git diff 6种场景介绍 3.1 工作区和暂存区差异对比 3.2 工作区和版本库之间差异 3.3 暂存区和版本库之间差异对比 3.4 本地版本库之间提交记录对比 3.5 不同分支对比 3.6 不同分支下同一文件对比 四、git diff 中 -x1,y…

【夜读】2022年最后10天,致自己!

昨日的烦恼&#xff0c;清零 人生这本书&#xff0c;翻过这一页&#xff0c;才能书写下一章。 想想这一年&#xff0c;你是不是曾为了些小事&#xff0c;把心情弄得一团糟&#xff1a;也许是工作中的一次失误&#xff0c;又或是聊天时的一次争执。 2022年的最后10天&#xff0c…

哈斯机床联网

一、设备信息确认 1、确认型号 哈斯的数控面板共有两种情况。 老版本&#xff1a; 新版本&#xff1a; 注&#xff1a;老版本通讯为串口&#xff0c;新版本通讯为网口。 2、确认通讯接口 1、数控面板的后面 老版本的串口一般都会引出在正后面的左侧位置&#xff0c;上面有…

用于开发语音 AI 应用程序的 GPU 加速 SDK

NVIDIA Riva 简介&#xff1a;用于开发语音 AI 应用程序的 GPU 加速 SDK 语音 AI 用于多种应用&#xff0c;包括联络中心的座席助理以增强人类座席的能力、智能虚拟助理 (IVA) 的语音界面以及视频会议中的实时字幕。 为了支持这些功能&#xff0c;语音 AI 技术包括自动语音识别…