From 7932d2ad315979f7b3b1800368cd199d390e0696 Mon Sep 17 00:00:00 2001 From: cwchen <1048842385@qq.com> Date: Tue, 9 Sep 2025 16:02:37 +0800 Subject: [PATCH] =?UTF-8?q?IP=E7=99=BD=E5=90=8D=E5=8D=95=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/bonus/BonusApplication.java | 2 + .../bonus/common/utils/IpWhitelistUtils.java | 81 +++++++++++ .../com/bonus/framework/auth/AuthLogic.java | 1 - .../bonus/framework/config/FilterConfig.java | 11 ++ .../framework/filter/IpWhitelistFilter.java | 133 ++++++++++++++++++ .../bonus/system/domain/SysIpWhitelist.java | 33 +++++ .../system/mapper/SysIpWhitelistMapper.java | 21 +++ .../service/ISysIpWhitelistService.java | 20 +++ .../impl/SysIpWhitelistServiceImpl.java | 41 ++++++ .../mapper/system/SysIpWhitelistMapper.xml | 36 +++++ 10 files changed, 378 insertions(+), 1 deletion(-) create mode 100644 bonus-common/src/main/java/com/bonus/common/utils/IpWhitelistUtils.java create mode 100644 bonus-framework/src/main/java/com/bonus/framework/filter/IpWhitelistFilter.java create mode 100644 bonus-system/src/main/java/com/bonus/system/domain/SysIpWhitelist.java create mode 100644 bonus-system/src/main/java/com/bonus/system/mapper/SysIpWhitelistMapper.java create mode 100644 bonus-system/src/main/java/com/bonus/system/service/ISysIpWhitelistService.java create mode 100644 bonus-system/src/main/java/com/bonus/system/service/impl/SysIpWhitelistServiceImpl.java create mode 100644 bonus-system/src/main/resources/mapper/system/SysIpWhitelistMapper.xml diff --git a/bonus-admin/src/main/java/com/bonus/BonusApplication.java b/bonus-admin/src/main/java/com/bonus/BonusApplication.java index f48863a..6dced2d 100644 --- a/bonus-admin/src/main/java/com/bonus/BonusApplication.java +++ b/bonus-admin/src/main/java/com/bonus/BonusApplication.java @@ -3,6 +3,7 @@ package com.bonus; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.scheduling.annotation.EnableScheduling; /** * 启动程序 @@ -10,6 +11,7 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; * @author bonus */ @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) +@EnableScheduling public class BonusApplication { public static void main(String[] args) diff --git a/bonus-common/src/main/java/com/bonus/common/utils/IpWhitelistUtils.java b/bonus-common/src/main/java/com/bonus/common/utils/IpWhitelistUtils.java new file mode 100644 index 0000000..6e75911 --- /dev/null +++ b/bonus-common/src/main/java/com/bonus/common/utils/IpWhitelistUtils.java @@ -0,0 +1,81 @@ +package com.bonus.common.utils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; +import java.net.InetAddress; +import java.net.UnknownHostException; +/** + * @className:IpWhitelistUtils + * @author:cwchen + * @date:2025-09-09-14:45 + * @version:1.0 + * @description: IP 工具类 + */ +@Slf4j +public class IpWhitelistUtils { + + /** + * 检查IP是否在范围内 + */ + public static boolean isIpInRange(String ip, String startIp, String endIp) { + try { + long ipLong = ipToLong(ip); + long startLong = ipToLong(startIp); + long endLong = ipToLong(endIp); + return ipLong >= startLong && ipLong <= endLong; + } catch (Exception e) { + log.error("IP范围检查失败: ip={}, start={}, end={}", ip, startIp, endIp, e); + return false; + } + } + + /** + * IP地址转long + */ + private static long ipToLong(String ip) { + String[] ipParts = ip.split("\\."); + long result = 0; + for (int i = 0; i < 4; i++) { + result = result << 8 | Integer.parseInt(ipParts[i]); + } + return result; + } + + /** + * 获取客户端真实IP + */ + public static String getClientIp(javax.servlet.http.HttpServletRequest request) { + String ip = request.getHeader("X-Forwarded-For"); + if (!StringUtils.isEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) { + // 多次反向代理后会有多个IP值,第一个为真实IP + int index = ip.indexOf(","); + if (index != -1) { + ip = ip.substring(0, index); + } + } + if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_CLIENT_IP"); + } + if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + } + if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip)) { + // 根据本机网卡取本机配置的IP + try { + InetAddress inet = InetAddress.getLocalHost(); + ip = inet.getHostAddress(); + } catch (UnknownHostException e) { + log.error("获取本地IP失败", e); + } + } + } + return ip; + } +} diff --git a/bonus-framework/src/main/java/com/bonus/framework/auth/AuthLogic.java b/bonus-framework/src/main/java/com/bonus/framework/auth/AuthLogic.java index b321688..2f99e41 100644 --- a/bonus-framework/src/main/java/com/bonus/framework/auth/AuthLogic.java +++ b/bonus-framework/src/main/java/com/bonus/framework/auth/AuthLogic.java @@ -194,7 +194,6 @@ public class AuthLogic { Set permissionList = getPermiList(); for (String permission : permissions) { - System.err.println(hasPermi(permissionList, permission)); if (!hasPermi(permissionList, permission)) { // throw new NotPermissionException(permission); throw new AccessDeniedException(permission+":越权访问"); diff --git a/bonus-framework/src/main/java/com/bonus/framework/config/FilterConfig.java b/bonus-framework/src/main/java/com/bonus/framework/config/FilterConfig.java index 110e18a..a2e5b35 100644 --- a/bonus-framework/src/main/java/com/bonus/framework/config/FilterConfig.java +++ b/bonus-framework/src/main/java/com/bonus/framework/config/FilterConfig.java @@ -6,6 +6,7 @@ import javax.servlet.DispatcherType; import com.bonus.common.filter.RequestCoverFilter; import com.bonus.common.filter.ResponseEncryptFilter; +import com.bonus.framework.filter.IpWhitelistFilter; import com.bonus.framework.filter.ReplayAttackFilter; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -94,4 +95,14 @@ public class FilterConfig return registration; }*/ + @Bean + public FilterRegistrationBean ipWhitelistFilterRegistration(IpWhitelistFilter filter) { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setFilter(filter); + registration.addUrlPatterns("/*"); + registration.setName("ipWhitelistFilter"); + registration.setOrder(1); // 设置较高的优先级 + return registration; + } + } diff --git a/bonus-framework/src/main/java/com/bonus/framework/filter/IpWhitelistFilter.java b/bonus-framework/src/main/java/com/bonus/framework/filter/IpWhitelistFilter.java new file mode 100644 index 0000000..8caa572 --- /dev/null +++ b/bonus-framework/src/main/java/com/bonus/framework/filter/IpWhitelistFilter.java @@ -0,0 +1,133 @@ +package com.bonus.framework.filter; + +import com.bonus.common.utils.IpWhitelistUtils; +import com.bonus.system.service.ISysIpWhitelistService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.StringUtils; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Date; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Component +public class IpWhitelistFilter implements Filter { + + @Autowired + private ISysIpWhitelistService whitelistService; + + private final AntPathMatcher pathMatcher = new AntPathMatcher(); + private final ConcurrentHashMap ipCache = new ConcurrentHashMap<>(); + private static final long CACHE_REFRESH_INTERVAL = TimeUnit.MINUTES.toMillis(1); + private static final long ENTRY_TTL = TimeUnit.MINUTES.toMillis(1); + private static final long refreshTime = 1000 * 60; + + private static final String[] EXCLUDE_PATHS = { + // 排除路径 + }; + + // 缓存条目类 + static class CacheEntry { + boolean allowed; + long timestamp; + + CacheEntry(boolean allowed) { + this.allowed = allowed; + this.timestamp = System.currentTimeMillis(); + } + + boolean isValid() { + return System.currentTimeMillis() - timestamp < ENTRY_TTL; + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + + // 检查是否在排除路径中 + String requestUri = httpRequest.getRequestURI(); + if (isExcludedPath(requestUri)) { + chain.doFilter(request, response); + return; + } + + // 获取客户端IP + String clientIp = IpWhitelistUtils.getClientIp(httpRequest); + + if (!StringUtils.hasText(clientIp)) { + sendForbiddenResponse(httpResponse, "无法获取客户端IP"); + return; + } + + // 检查IP是否在白名单中 + if (!isIpWhitelisted(clientIp)) { + log.warn("IP访问被拒绝: {} - {}", clientIp, requestUri); + sendForbiddenResponse(httpResponse, "请求IP不在白名单中: " + clientIp); + return; + } + + chain.doFilter(request, response); + } + + private boolean isExcludedPath(String requestUri) { + for (String pattern : EXCLUDE_PATHS) { + if (pathMatcher.match(pattern, requestUri)) { + return true; + } + } + return false; + } + + private boolean isIpWhitelisted(String ip) { + CacheEntry entry = ipCache.get(ip); + + // 如果缓存存在且有效,直接返回 + if (entry != null && entry.isValid()) { + return entry.allowed; + } + + // 缓存不存在或已过期,查询数据库 + boolean allowed = whitelistService.isIpAllowed(ip, new Date()); + ipCache.put(ip, new CacheEntry(allowed)); + return allowed; + } + + // 定时刷新缓存(可选) + @Scheduled(fixedRate = refreshTime) + public void refreshCache() { + log.info("开始定时刷新IP白名单缓存..."); + int sizeBefore = ipCache.size(); + ipCache.clear(); + log.info("IP白名单缓存刷新完成,清理了 {} 个缓存条目", sizeBefore); + } + + private void sendForbiddenResponse(HttpServletResponse response, String message) throws IOException { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write("{\"code\": 403, \"msg\": \"" + message + "\",\"isIp\": true}"); + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + log.info("IP白名单过滤器初始化完成,缓存刷新间隔: {} 分钟", + TimeUnit.MILLISECONDS.toMinutes(CACHE_REFRESH_INTERVAL)); + } + + @Override + public void destroy() { + ipCache.clear(); + log.info("IP白名单过滤器销毁完成"); + } +} \ No newline at end of file diff --git a/bonus-system/src/main/java/com/bonus/system/domain/SysIpWhitelist.java b/bonus-system/src/main/java/com/bonus/system/domain/SysIpWhitelist.java new file mode 100644 index 0000000..04600fb --- /dev/null +++ b/bonus-system/src/main/java/com/bonus/system/domain/SysIpWhitelist.java @@ -0,0 +1,33 @@ +package com.bonus.system.domain; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import java.util.Date; +/** + * @className:SysIpWhitelist + * @author:cwchen + * @date:2025-09-09-14:41 + * @version:1.0 + * @description:白名单配置 + */ +@Data +public class SysIpWhitelist { + + private Long id; + private String ipAddress; + private String ipRangeStart; + private String ipRangeEnd; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date accessStartTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date accessEndTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createdAt; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updatedAt; + + private String status; +} diff --git a/bonus-system/src/main/java/com/bonus/system/mapper/SysIpWhitelistMapper.java b/bonus-system/src/main/java/com/bonus/system/mapper/SysIpWhitelistMapper.java new file mode 100644 index 0000000..5cd2551 --- /dev/null +++ b/bonus-system/src/main/java/com/bonus/system/mapper/SysIpWhitelistMapper.java @@ -0,0 +1,21 @@ +package com.bonus.system.mapper; + +import com.bonus.system.domain.SysIpWhitelist; +import org.apache.ibatis.annotations.Param; + +import java.util.Date; +import java.util.List; + +/** + * @className:SysIpWhitelistMapper + * @author:cwchen + * @date:2025-09-09-14:42 + * @version:1.0 + * @description:白名单配置-数据层 + */ +public interface SysIpWhitelistMapper { + + List selectAllEnabledWhitelist(); + List selectEnabledWhitelistByIp(@Param("ip") String ip); + int existsValidWhitelist(@Param("ip") String ip, @Param("currentTime") Date currentTime); +} diff --git a/bonus-system/src/main/java/com/bonus/system/service/ISysIpWhitelistService.java b/bonus-system/src/main/java/com/bonus/system/service/ISysIpWhitelistService.java new file mode 100644 index 0000000..344e351 --- /dev/null +++ b/bonus-system/src/main/java/com/bonus/system/service/ISysIpWhitelistService.java @@ -0,0 +1,20 @@ +package com.bonus.system.service; + +import com.bonus.system.domain.SysIpWhitelist; + +import java.util.Date; +import java.util.List; + +/** + * @className:ISysIpWhitelistService + * @author:cwchen + * @date:2025-09-09-14:49 + * @version:1.0 + * @description:白名单配置 + */ +public interface ISysIpWhitelistService { + + List getAllEnabledWhitelist(); + boolean isIpAllowed(String ip, Date currentTime); + void refreshCache(); +} diff --git a/bonus-system/src/main/java/com/bonus/system/service/impl/SysIpWhitelistServiceImpl.java b/bonus-system/src/main/java/com/bonus/system/service/impl/SysIpWhitelistServiceImpl.java new file mode 100644 index 0000000..df14f09 --- /dev/null +++ b/bonus-system/src/main/java/com/bonus/system/service/impl/SysIpWhitelistServiceImpl.java @@ -0,0 +1,41 @@ +package com.bonus.system.service.impl; + +import com.bonus.system.domain.SysIpWhitelist; +import com.bonus.system.mapper.SysIpWhitelistMapper; +import com.bonus.system.service.ISysIpWhitelistService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.Date; +import java.util.List; + +/** + * @className:SysIpWhitelistServiceImpl + * @author:cwchen + * @date:2025-09-09-14:50 + * @version:1.0 + * @description:白名单配置业务逻辑层 + */ +@Service +public class SysIpWhitelistServiceImpl implements ISysIpWhitelistService { + + @Autowired + private SysIpWhitelistMapper whitelistMapper; + + @Override + public List getAllEnabledWhitelist() { + return whitelistMapper.selectAllEnabledWhitelist(); + } + + @Override + public boolean isIpAllowed(String ip, Date currentTime) { + int count = whitelistMapper.existsValidWhitelist(ip, currentTime); + return count > 0; + } + + @Override + public void refreshCache() { + // 可以在这里实现缓存刷新逻辑 + } +} diff --git a/bonus-system/src/main/resources/mapper/system/SysIpWhitelistMapper.xml b/bonus-system/src/main/resources/mapper/system/SysIpWhitelistMapper.xml new file mode 100644 index 0000000..3d0dfa4 --- /dev/null +++ b/bonus-system/src/main/resources/mapper/system/SysIpWhitelistMapper.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + \ No newline at end of file