title: Obtain the real IP address and the corresponding province city and system browser information through Request
date: 2022-12-16 16:20:26
tags:
- GeoIP2
- UserAgentUtils
categories: - Development practice
cover: https://cover.png
feature: false
1. Obtain real IP address
1.1 Code
The code is as follows, here is the encapsulation method CommonUtil.isBlank()
for judging empty space
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 Explanation
1. First, obtain the IP address at the 0th position X-Forwarded-For
in , which can represent the real client IP in the HTTP extension protocol, as in the following example:
X-Forwarded-For: client, proxy1, proxy2, proxy…
2. If you can’t X-Forwarded-For
get it , get it X-Real-IP
, X-Real-IP
if you can’t get it, get it in turn Proxy-Client-IP
, WL-Proxy-Client-IP
, HTTP_CLIENT_IP
, HTTP_X_FORWARDED_FOR
. Finally, if you can’t get it, request.getRemoteAddr()
you can get
X-Real-IP
: Record the real IP of the client requesting,X-Forwarded-For
similarProxy-Client-IP
: The IP of the proxy client. If the real IP of the client cannot be obtained, only the IP of the proxy client can be obtained.WL-Proxy-Client-IP
: The parameters used to obtain the real IP under WeblogicHTTP_CLIENT_IP
,HTTP_X_FORWARDED_FOR
: It can be understood thatX-Forwarded-For
they are usages in PHP
3. When request.getRemoteAddr()
obtaining , what is obtained is 0:0:0:0:0:0:0:1 of IPV6, which needs to be converted to 127.0.0.1 of IPV4
1.3 Nginx configuration request header parameters
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. Get province and city information by IP address
Divided into two ways, online and offline:
1. Use the API provided by the online third party:
- ip-api.com
- ip.taotao.com
- baidu map api
- Sina iplookup
2. Use the offline query method:
- Innocence library
- GeoLite2
- Evan Technology
The specific data richness, accuracy and query speed can be collected by yourself. Since GeoLite2 is free, and the offline query speed is faster and stable, and there is no limit to the number of API concurrency, etc., GeoLite2 is used here to obtain province and city information, and the data richness is also relatively high
2.1 Download GeoLite2 City library
The GeoLite database is owned by MaxMind. The GeoLite database has an open source version and a paid version. The open source version is used here. GeoLite has been updated to 2, so download the GeoLite2 City library. The download address is as follows: GeoLite2 Free Geolocation Data | MaxMind Developer Portal
Click Download Files on the page
If you are not logged in, you will be redirected to the login page
If you do not have an account, click Create
There will be several account forms here, choose to log in to the free GeoLite2 database and Web service
After filling in the corresponding information, an email to set the password will be sent, click the link to set the password
Click to log in when finished
Enter the user name and password to log in, the user name is the email address
select download database
Select GZIP download
After the download is complete, you will get a tar package file
After decompression, there is the database file we need (Windows can be decompressed with 7-Zip)
2.2 use
2.2.1 Put the file into the project root path
2.2.2 Introducing dependencies
It seems that version 3.0 or above supports JDK 11 at least, and if it is JDK 8, it can use 2.16.1 at most
<!-- GeoIP2 -->
<dependency>
<groupId>com.maxmind.geoip2</groupId>
<artifactId>geoip2</artifactId>
<version>2.16.1</version>
</dependency>
2.2.3 Code
Here new DatabaseReader.Builder(database).build()
supports two types, one is File and the other is InputStream. Both are available for local projects, but there may be problems obtaining File type files when they are packaged and run on the server. It is best to obtain the build through streaming
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 Encapsulated as a tool class
1. Entity class
@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. Packaging tools
The previous method of obtaining the IP address is also encapsulated here, and LogUtil is the encapsulated log tool class
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. Obtain system and browser information
This type of information is generally obtained through the UA (User Agent) identification. The Chinese name of User Agent is User Agent, or UA for short. It is a special string header that enables the server to identify the operating system and version, CPU type, browser and version, browser rendering engine, browser language, and browser version used by the client. plug-ins, etc.
First get the User-Agent in the request header
String ua = request.getHeader("User-Agent");
Introduce UserAgentUtils dependency
<!-- UserAgentUtils -->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>1.21</version>
</dependency>
Use the provided UserAgent class to parse the ua string
UserAgent userAgent = UserAgent.parseUserAgentString(ua);
// 操作系统
userAgent.getOperatingSystem().getName()
// 浏览器
userAgent.getBrowser().getName()