Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</parent>
<groupId>com.jmal</groupId>
<artifactId>jmalcloud</artifactId>
<version>2.16.5</version>
<version>2.16.6</version>
<name>jmalcloud</name>
<description>Cloud Disk</description>
<packaging>jar</packaging>
Expand Down
90 changes: 34 additions & 56 deletions src/main/java/com/jmal/clouddisk/service/impl/LogService.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
import com.jmal.clouddisk.model.LogOperationDTO;
import com.jmal.clouddisk.model.rbac.ConsumerDO;
import com.jmal.clouddisk.service.Constants;
import com.jmal.clouddisk.util.IPUtil;
import com.jmal.clouddisk.util.ResponseResult;
import com.jmal.clouddisk.util.ResultUtil;
import com.jmal.clouddisk.util.TimeUntils;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
Expand All @@ -26,6 +28,7 @@
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.List;
Expand All @@ -41,8 +44,6 @@
@RequiredArgsConstructor
public class LogService {

private static final int REGION_LENGTH = 5;

private final ILogDAO logDAO;

private final UserLoginHolder userLoginHolder;
Expand Down Expand Up @@ -145,43 +146,12 @@ public LogOperation getLogOperation(HttpServletRequest request, LogOperation log
// 请求方式
logOperation.setMethod(request.getMethod());
// 客户端ip
String ip = getIpAddress(request);
String ip = IPUtil.getClientIP(request);
logOperation.setIp(ip);
setIpInfo(logOperation, ip);
return logOperation;
}

private String getIpAddress(HttpServletRequest request) {
String ip = request.getRemoteHost();
if (CharSequenceUtil.isNotBlank(ip)) {
return ip;
}
ip = request.getHeader("x-forwarded-for");
if (!CharSequenceUtil.isBlank(ip) && (ip.contains(","))) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
ip = ip.split(",")[0];
}
if (CharSequenceUtil.isBlank(ip)) {
ip = request.getHeader("X-real-ip");
}
if (CharSequenceUtil.isBlank(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (CharSequenceUtil.isBlank(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (CharSequenceUtil.isBlank(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (CharSequenceUtil.isBlank(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (CharSequenceUtil.isBlank(ip)) {
ip = request.getHeader("X-Real-IP");
}
return ip;
}

/***
* 设置IP详细信息
* @param logOperation LogOperation
Expand All @@ -199,33 +169,30 @@ private void setIpInfo(LogOperation logOperation, String ip) {
}
}

/***
* 解析IP区域信息
/**
* 解析IP区域信息, region格式: 国家|区域|省份|城市|运营商
*/
public LogOperation.IpInfo region2IpInfo(String region) {
LogOperation.IpInfo ipInfo = new LogOperation.IpInfo();
String[] r = region.split("\\|");
if (r.length != REGION_LENGTH) return ipInfo;
String country = r[0];
if (!Constants.REGION_DEFAULT.equals(country)) {
ipInfo.setCountry(country);
}
String area = r[1];
if (!Constants.REGION_DEFAULT.equals(area)) {
ipInfo.setArea(area);
}
String province = r[2];
if (!Constants.REGION_DEFAULT.equals(province)) {
ipInfo.setProvince(province);
}
String city = r[3];
if (!Constants.REGION_DEFAULT.equals(city)) {
ipInfo.setCity(city);

if (CharSequenceUtil.isBlank(region)) {
return ipInfo;
}
String operators = r[4];
if (!Constants.REGION_DEFAULT.equals(operators)) {
ipInfo.setOperators(operators);

String[] parts = IPUtil.SPLIT_PATTERN.split(region);

if (parts.length != IPUtil.REGION_LENGTH) {
return ipInfo;
}

String def = Constants.REGION_DEFAULT;

if (!def.equals(parts[0])) ipInfo.setCountry(parts[0]);
if (!def.equals(parts[1])) ipInfo.setArea(parts[1]);
if (!def.equals(parts[2])) ipInfo.setProvince(parts[2]);
if (!def.equals(parts[3])) ipInfo.setCity(parts[3]);
if (!def.equals(parts[4])) ipInfo.setOperators(parts[4]);

return ipInfo;
}

Expand Down Expand Up @@ -347,4 +314,15 @@ public ResponseResult<List<LogOperationDTO>> getFileOperationHistory(LogOperatio
public long getVisitsByUrl(String url) {
return logDAO.countByUrl(url);
}

@PreDestroy
public void destroy() {
if (ipSearcher != null) {
try {
ipSearcher.close();
} catch (IOException e) {
log.error("failed to close ip searcher", e);
}
}
}
}
63 changes: 63 additions & 0 deletions src/main/java/com/jmal/clouddisk/util/IPUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.jmal.clouddisk.util;

import jakarta.servlet.http.HttpServletRequest;

import java.util.regex.Pattern;

public final class IPUtil {

private IPUtil() {
}

public static final int REGION_LENGTH = 5;
public static final Pattern SPLIT_PATTERN = Pattern.compile("\\|");

private static final String[] IP_HEADERS = {
"CF-Connecting-IP",
"X-Real-IP",
"X-Forwarded-For",
"Proxy-Client-IP",
"WL-Proxy-Client-IP"
};

/**
* 获取客户端真实IP
* 自动处理大小写不敏感的请求头
*
* @param request HTTP请求对象
* @return 客户端IP地址
*/
public static String getClientIP(HttpServletRequest request) {
for (String header : IP_HEADERS) {
String ip = request.getHeader(header);
if (ip == null) {
continue;
}
String firstIp = extractFirstIp(ip);
if (isValidIp(firstIp)) {
return firstIp;
}
}
return request.getRemoteAddr();
}

/**
* 提取第一个IP(处理逗号分隔的多IP情况)
*/
private static String extractFirstIp(String ip) {
if (ip.contains(",")) {
int commaIndex = ip.indexOf(',');
return ip.substring(0, commaIndex).trim();
}
return ip.trim();
}

/**
* 检查IP是否有效
*/
private static boolean isValidIp(String ip) {
return ip != null
&& !ip.isEmpty()
&& !"unknown".equalsIgnoreCase(ip);
}
Comment on lines +58 to +62
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

当前的IP验证逻辑 (isValidIp) 过于宽松,只检查了字符串是否为非空且不等于 'unknown'。这可能允许格式不正确的字符串(例如,恶意注入的文本)被当作有效的IP地址处理和记录,存在潜在的日志注入风险。

为了增强安全性和数据准确性,建议使用 hutool 库中的 NetUtil.isIP() 方法来进行更严格的IP地址格式验证。这将确保只接受有效的IPv4或IPv6地址。

为了方便阅读,建议在文件顶部添加 import cn.hutool.core.net.NetUtil;,然后在代码中使用 NetUtil.isIP(ip)

    private static boolean isValidIp(String ip) {
        return ip != null
                && !ip.isEmpty()
                && !"unknown".equalsIgnoreCase(ip)
                && cn.hutool.core.net.NetUtil.isIP(ip);
    }

}