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

news2025/2/5 6:01:16

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 扩展协议中能表示真实的客户端 IP
        String 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-IP
        ipAddress = request.getHeader("X-Real-IP");
        if (CommonUtil.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {
            // 如果 X-Real-IP 获取不到, 就去获取 Proxy-Client-IP
            ipAddress = request.getHeader("Proxy-Client-IP");
        }
        if (CommonUtil.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {
            // 如果 Proxy-Client-IP 获取不到, 就去获取 WL-Proxy-Client-IP
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if (CommonUtil.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {
            // 如果 WL-Proxy-Client-IP 获取不到, 就去获取 HTTP_CLIENT_IP
            ipAddress = request.getHeader("HTTP_CLIENT_IP");
        }
        if (CommonUtil.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {
            // 如果 HTTP_CLIENT_IP 获取不到, 就去获取 HTTP_X_FORWARDED_FOR
            ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (CommonUtil.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {
            // 都获取不到, 最后才通过 request.getRemoteAddr() 获取IP
            ipAddress = 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.2222
        System.out.println(location.getLongitude()); // 经度 121.4581
    }
}

在这里插入图片描述

2.3 封装成工具类

1、实体类

@Data
@TableName("login_geo")
public class LoginGeoDO {

    // 主键ID
    private 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 扩展协议中能表示真实的客户端 IP
        String 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-IP
        ipAddress = request.getHeader("X-Real-IP");
        if (CommonUtil.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {
            // 如果 X-Real-IP 获取不到, 就去获取 Proxy-Client-IP
            ipAddress = request.getHeader("Proxy-Client-IP");
        }
        if (CommonUtil.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {
            // 如果 Proxy-Client-IP 获取不到, 就去获取 WL-Proxy-Client-IP
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if (CommonUtil.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {
            // 如果 WL-Proxy-Client-IP 获取不到, 就去获取 HTTP_CLIENT_IP
            ipAddress = request.getHeader("HTTP_CLIENT_IP");
        }
        if (CommonUtil.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {
            // 如果 HTTP_CLIENT_IP 获取不到, 就去获取 HTTP_X_FORWARDED_FOR
            ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (CommonUtil.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {
            // 都获取不到, 最后才通过 request.getRemoteAddr() 获取IP
            ipAddress = 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.coloradmin.cn/o/103255.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

set、map使用及其细节

&#x1f9f8;&#x1f9f8;&#x1f9f8;各位大佬大家好&#xff0c;我是猪皮兄弟&#x1f9f8;&#x1f9f8;&#x1f9f8; 文章目录一、两个概念①关联式容器与序列式容器②键值对二、set、map①setset的两种遍历方式set的升降序排序set的eraseset的count②mapSGI-STL中键值…

ISCSLP论文介绍|利用BERT提高语种识别性能

本文介绍清华大学语音与音频技术实验室&#xff08;SATLab&#xff09;ISCSLP 2022录用论文。BERT-LID: Leveraging BERT to Improve Spoken Language Identification。这篇文章将BERT模型引入到语种识别领域。利用BERT模型的优越性&#xff0c;再结合下游不同的神经网络模型&a…

OFDM系统中基于dmrs导频的时间跟踪、频率跟踪算法

目录发射端模型假设接收端模型假设时延估计&#xff08;时间跟踪&#xff09;频偏估计&#xff08;频率跟踪&#xff09;在OFDM系统中&#xff0c;为了估计出信号传输遇到的时间偏移和频率偏移&#xff0c;可以采用导频进行估计。 发射端模型假设 我们假设如下模型&#xff1…

如何熟练掌握运用Delft3D建模、水动力模拟方法及在地表水环境影响评价中的实践技术

Delft3D是由荷兰Delft大学WL Delft Hydraulics开发的一套功能强大的软件包&#xff0c;主要应用于自由地表水环境。该软件具有灵活的框架&#xff0c;能够模拟二维和三维的水流、波浪、水质、生态、泥沙输移及床底地貌&#xff0c;以及各个过程之间的相互作用。它是国际上最为先…

使用 C# Graphics 绘图来绘制一个足球

背景 2022卡塔尔世界杯是足球爱好者的狂欢&#xff0c;这与我毫无关系&#xff0c;作为一个缺乏运动的人&#xff0c;还是不要去看人家玩命的运动了。虽然不看球&#xff0c;不过这波热度的持续冲击&#xff0c;还是让我在朋友圈刷到了结局 ———— 球王梅西如愿以偿捧得金杯…

Bootstrap5 下拉菜单

下拉菜单可以是单选下拉菜单&#xff0c;也可以是多选的下拉菜单。 单选下拉菜单&#xff1a; 多选下拉菜单&#xff1a; 在 Bootstrap5 中下拉菜单 <select> 元素可以使用 .form-select 类来渲染 : 实例 <select class"form-select"> <option>…

内核启动过程分析

目录 一、内核源码获取查看 1.1、Source Insight使用 二、查看链接脚本 三、分析head.S 3.1、到入口前代码 3.2、内核启动的汇编阶段 四、main.c内核启动的c语言阶段 4.1、内核打印函数printk 4.2、启动信息 五、rest_init函数 5.1、进程0、进程1、进程2​编辑 5.2、…

详细复习云开发~小程序【搜索功能、登陆注册功能、点赞收藏评论功能、评论功能、CMS网页版管理后台】

文章目录一&#xff0c;搜索功能1-1&#xff0c;需求1-2&#xff0c;实现原理1-3&#xff0c;模糊搜索的代码实现1-3-1&#xff0c;模糊搜索单个字段1-3-2&#xff0c;模糊搜索多个字段&#xff08;满足一个即可&#xff09;1-3-3&#xff0c;模糊搜索多个字段&#xff08;要同…

python获取redis memory使用情况

项目研发过程中&#xff0c;用到Python操作Redis场景&#xff0c;记录学习过程中的心得体会。 一、环境搭建 Windows Anaconda3安装redis第3方包&#xff0c;pip install -u redis pip install -u # 升级安装 linux下查看redis配置信息bind 127.0.0.1 # 表示只允许本地访问,…

AssetBundle依赖打包有哪些注意点

1&#xff09;AssetBundle依赖打包有哪些注意点 ​2&#xff09;子程序集如何引用Assembly-CSharp.dll 3&#xff09;Unity的线性空间下自定义贴图在PS中修改问题 4&#xff09;如何关闭视锥体剔除 这是第318篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化…

CSS3之2D转换

文章目录2D转换一、transform1-1.2D转换之移动translate1-2.translate&#xff08;百分比&#xff09;盒子居中二、rotate2-1.旋转效果2-2.transform-origin设置旋转中心点三、2D转换之缩放scale四、2D转换综合写法总结2D转换 转换&#xff08;trabsform&#xff09;是CSS3中具…

SPDK块设备bdev简介

介绍 SPDK Bdev架构 SPDK块设备层&#xff08;通常简称为bdev&#xff09;是一个C库&#xff0c;旨在等同于操作系统块存储层&#xff0c;该层通常位于传统内核存储堆栈中设备驱动程序的正上方。具体来说&#xff0c;此库提供以下功能&#xff1a; 一种可插拔模块API&#xf…

Effective C++(二):构造/析构/赋值运算

个人读书记录&#xff0c;不适用教学内容。 目录 条款05&#xff1a;了解C默默编写并调用了哪些函数 条款06&#xff1a;若不想使用编译器自动生成的函数&#xff0c;就该明确拒绝 条款07&#xff1a;为多态基类声明virtual析构函数 条款08&#xff1a;别让异常逃离析构函数…

设备巡检系统哪个软件好?

本文将介绍&#xff1a;1、好用的设备巡检系统&#xff0c;2、评价设备巡检系统好用的标准&#xff1b;3、做好设备管理的几大关键 现今许多企业&#xff0c;尤其是制造业&#xff0c;规模日趋机械化、自动化、大型化、高速化和复杂化&#xff0c;对设备巡检工作的要求越加精细…

微信小程序网络请求

小程序的网络请求的文档是很少的&#xff0c;只提供了API&#xff0c;例子也不举。 基本使用 最简单的使用如下&#xff1a; 就是调用wx.request方法&#xff0c;这个方法已经能够应付大部分的网络请求了。 onLoad() {wx.request({url: http://123.207.32.32:1888/api/city/a…

我学python的那段日子(五)python中的函数

python中的函数 1.1自定义函数 1.1.1 函数的定义 ​ 和Java一样&#xff0c;python也有内置函数和自定义函数&#xff0c;内置函数是python已经定义好的函数&#xff0c;而自定义函数则是我们根据需要自己定义的函数。 ​ 语法 def 函数名称(参数): ​ 函数体 ​ 其中参数的…

一个转行学习前端的初学者,应该如何计划自己的学习规划?

就目前的发展来看&#xff0c;在语言排行中&#xff0c;前端多年霸主的地位还未被撼动&#xff0c;前端语言的特点就注定了很多公司会选用它&#xff0c;强大的第三方库使前端正在web端的强大无法被替代。目前由于大数据&#xff0c;人工智能的发展&#xff0c;互联网成为了发展…

Bootstrap5 复选框与单选框

如果您希望用户从预设选项列表中选择任意数量的选项&#xff0c;可以使用复选框&#xff1a; 实例 <div class"form-check"> <input class"form-check-input" type"checkbox" id"check1" name"option1" value&quo…

【Python机器学习】朴素贝叶斯分类的讲解及预测决策实战(图文解释 附源码)

需要代码请点赞关注收藏后评论区留言私信~~~ 朴素贝叶斯分类 朴素贝叶斯&#xff08;nave Bayes&#xff09;分类是基于贝叶斯定理与特征条件独立假定的分类方法。 设试验E的样本空间为S&#xff0c;A为E的事件&#xff0c;B_1&#xff0c;B_2&#xff0c;⋯&#xff0c;B_n为…

深入浅出理解Java并发AQS的共享锁模式

自定义共享锁例子 首先我们通过AQS实现一个非常最最最轻量简单的共享锁例子&#xff0c;帮助大家对共享锁有一个整体的感知。 Slf4j public class ShareLock {/*** 共享锁帮助类*/private static class ShareSync extends AbstractQueuedSynchronizer {private int lockCount…