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

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()
posted @ 2022-12-21 14:26  凡223  阅读(567)  评论(0编辑  收藏  举报