IP白名单配置

This commit is contained in:
cwchen 2025-09-09 16:02:37 +08:00
parent efa9a9f5c4
commit 7932d2ad31
10 changed files with 378 additions and 1 deletions

View File

@ -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)

View File

@ -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;
}
}

View File

@ -194,7 +194,6 @@ public class AuthLogic {
Set<String> permissionList = getPermiList();
for (String permission : permissions) {
System.err.println(hasPermi(permissionList, permission));
if (!hasPermi(permissionList, permission)) {
// throw new NotPermissionException(permission);
throw new AccessDeniedException(permission+":越权访问");

View File

@ -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<IpWhitelistFilter> ipWhitelistFilterRegistration(IpWhitelistFilter filter) {
FilterRegistrationBean<IpWhitelistFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(filter);
registration.addUrlPatterns("/*");
registration.setName("ipWhitelistFilter");
registration.setOrder(1); // 设置较高的优先级
return registration;
}
}

View File

@ -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<String, CacheEntry> 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白名单过滤器销毁完成");
}
}

View File

@ -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;
}

View File

@ -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<SysIpWhitelist> selectAllEnabledWhitelist();
List<SysIpWhitelist> selectEnabledWhitelistByIp(@Param("ip") String ip);
int existsValidWhitelist(@Param("ip") String ip, @Param("currentTime") Date currentTime);
}

View File

@ -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<SysIpWhitelist> getAllEnabledWhitelist();
boolean isIpAllowed(String ip, Date currentTime);
void refreshCache();
}

View File

@ -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<SysIpWhitelist> 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() {
// 可以在这里实现缓存刷新逻辑
}
}

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bonus.system.mapper.SysIpWhitelistMapper">
<select id="selectAllEnabledWhitelist" resultType="SysIpWhitelist">
SELECT *
FROM da_ky_sys_ip_whitelist
WHERE status = '0'
</select>
<select id="selectEnabledWhitelistByIp" resultType="SysIpWhitelist">
SELECT *
FROM da_ky_sys_ip_whitelist
WHERE status = '0'
</select>
<select id="existsValidWhitelist" resultType="int">
SELECT COUNT(*)
FROM da_ky_sys_ip_whitelist
WHERE status = '0'
AND (
(ip_address = #{ip} AND ip_address IS NOT NULL)
OR
(ip_range_start IS NOT NULL AND ip_range_end IS NOT NULL
AND INET_ATON(#{ip}) BETWEEN INET_ATON(ip_range_start) AND INET_ATON(ip_range_end))
)
AND (
(access_start_time IS NULL AND access_end_time IS NULL)
OR
(#{currentTime} BETWEEN access_start_time AND access_end_time)
)
</select>
</mapper>