IP白名单配置
This commit is contained in:
parent
efa9a9f5c4
commit
7932d2ad31
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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+":越权访问");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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白名单过滤器销毁完成");
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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() {
|
||||
// 可以在这里实现缓存刷新逻辑
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
Loading…
Reference in New Issue