Browse Source

Changes

master
主管理员 6 days ago
parent
commit
db1f059908
4 changed files with 620 additions and 0 deletions
  1. +178
    -0
      jeecg-boot-module-system/src/main/java/org/jeecg/modules/system/controller/IpSecurityController.java
  2. +192
    -0
      jeecg-boot-module-system/src/main/java/org/jeecg/modules/system/security/IpAccessLimitInterceptor.java
  3. +211
    -0
      jeecg-boot-module-system/src/main/java/org/jeecg/modules/system/security/IpSecurityService.java
  4. +39
    -0
      jeecg-boot-module-system/src/main/java/org/jeecg/modules/system/security/WebSecurityConfig.java

+ 178
- 0
jeecg-boot-module-system/src/main/java/org/jeecg/modules/system/controller/IpSecurityController.java View File

@ -0,0 +1,178 @@
package org.jeecg.modules.system.controller;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.modules.system.security.IpSecurityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* IP安全管理控制器
* 提供IP黑白名单管理功能
*
* @author system
* @date 2024
*/
@Api(tags = "IP安全管理")
@RestController
@RequestMapping("/sys/ip/manage")
@Slf4j
public class IpSecurityController {
@Autowired
private IpSecurityService ipSecurityService;
@AutoLog(value = "获取黑名单IP列表")
@ApiOperation(value = "获取黑名单IP列表", notes = "获取所有被禁止的IP地址")
@GetMapping("/blacklist")
public Result<List<IpSecurityService.IpInfo>> getBlacklistIps() {
try {
List<IpSecurityService.IpInfo> blacklistIps = ipSecurityService.getBlacklistIps();
return Result.ok(blacklistIps);
} catch (Exception e) {
log.error("获取黑名单IP列表失败", e);
return Result.error("获取黑名单IP列表失败: " + e.getMessage());
}
}
@AutoLog(value = "获取白名单IP列表")
@ApiOperation(value = "获取白名单IP列表", notes = "获取所有白名单IP地址")
@GetMapping("/whitelist")
public Result<List<IpSecurityService.IpInfo>> getWhitelistIps() {
try {
List<IpSecurityService.IpInfo> whitelistIps = ipSecurityService.getWhitelistIps();
return Result.ok(whitelistIps);
} catch (Exception e) {
log.error("获取白名单IP列表失败", e);
return Result.error("获取白名单IP列表失败: " + e.getMessage());
}
}
@AutoLog(value = "添加IP到黑名单")
@ApiOperation(value = "添加IP到黑名单", notes = "手动将IP地址加入黑名单")
@PostMapping("/blacklist/add")
public Result<String> addToBlacklist(
@ApiParam(value = "IP地址", required = true) @RequestParam String ip,
@ApiParam(value = "禁止时长(分钟)", required = false) @RequestParam(defaultValue = "30") Integer durationMinutes) {
try {
ipSecurityService.addToBlacklist(ip, durationMinutes);
return Result.ok("IP " + ip + " 已成功加入黑名单");
} catch (Exception e) {
log.error("添加IP到黑名单失败", e);
return Result.error("添加IP到黑名单失败: " + e.getMessage());
}
}
@AutoLog(value = "从黑名单移除IP")
@ApiOperation(value = "从黑名单移除IP", notes = "从黑名单中移除指定IP地址")
@DeleteMapping("/blacklist/remove")
public Result<String> removeFromBlacklist(@ApiParam(value = "IP地址", required = true) @RequestParam String ip) {
try {
ipSecurityService.removeFromBlacklist(ip);
return Result.ok("IP " + ip + " 已从黑名单中移除");
} catch (Exception e) {
log.error("从黑名单移除IP失败", e);
return Result.error("从黑名单移除IP失败: " + e.getMessage());
}
}
@AutoLog(value = "添加IP到白名单")
@ApiOperation(value = "添加IP到白名单", notes = "将IP地址加入白名单,跳过访问限制")
@PostMapping("/whitelist/add")
public Result<String> addToWhitelist(@ApiParam(value = "IP地址", required = true) @RequestParam String ip) {
try {
ipSecurityService.addToWhitelist(ip);
return Result.ok("IP " + ip + " 已成功加入白名单");
} catch (Exception e) {
log.error("添加IP到白名单失败", e);
return Result.error("添加IP到白名单失败: " + e.getMessage());
}
}
@AutoLog(value = "从白名单移除IP")
@ApiOperation(value = "从白名单移除IP", notes = "从白名单中移除指定IP地址")
@DeleteMapping("/whitelist/remove")
public Result<String> removeFromWhitelist(@ApiParam(value = "IP地址", required = true) @RequestParam String ip) {
try {
ipSecurityService.removeFromWhitelist(ip);
return Result.ok("IP " + ip + " 已从白名单中移除");
} catch (Exception e) {
log.error("从白名单移除IP失败", e);
return Result.error("从白名单移除IP失败: " + e.getMessage());
}
}
@AutoLog(value = "获取IP访问统计")
@ApiOperation(value = "获取IP访问统计", notes = "获取指定IP的访问统计信息")
@GetMapping("/stats")
public Result<IpSecurityService.IpAccessStats> getIpAccessStats(@ApiParam(value = "IP地址", required = true) @RequestParam String ip) {
try {
IpSecurityService.IpAccessStats stats = ipSecurityService.getIpAccessStats(ip);
return Result.ok(stats);
} catch (Exception e) {
log.error("获取IP访问统计失败", e);
return Result.error("获取IP访问统计失败: " + e.getMessage());
}
}
@AutoLog(value = "获取当前IP访问统计")
@ApiOperation(value = "获取当前IP访问统计", notes = "获取当前请求IP的访问统计信息")
@GetMapping("/current-stats")
public Result<IpSecurityService.IpAccessStats> getCurrentIpAccessStats(HttpServletRequest request) {
try {
String clientIp = getClientIpAddress(request);
IpSecurityService.IpAccessStats stats = ipSecurityService.getIpAccessStats(clientIp);
return Result.ok(stats);
} catch (Exception e) {
log.error("获取当前IP访问统计失败", e);
return Result.error("获取当前IP访问统计失败: " + e.getMessage());
}
}
@AutoLog(value = "清除IP访问记录")
@ApiOperation(value = "清除IP访问记录", notes = "清除指定IP的所有访问记录")
@DeleteMapping("/clear-records")
public Result<String> clearIpAccessRecords(@ApiParam(value = "IP地址", required = true) @RequestParam String ip) {
try {
ipSecurityService.clearIpAccessRecords(ip);
return Result.ok("IP " + ip + " 的访问记录已清除");
} catch (Exception e) {
log.error("清除IP访问记录失败", e);
return Result.error("清除IP访问记录失败: " + e.getMessage());
}
}
/**
* 获取客户端真实IP地址
*/
private String getClientIpAddress(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty() && !"unknown".equalsIgnoreCase(xForwardedFor)) {
return xForwardedFor.split(",")[0].trim();
}
String xRealIp = request.getHeader("X-Real-IP");
if (xRealIp != null && !xRealIp.isEmpty() && !"unknown".equalsIgnoreCase(xRealIp)) {
return xRealIp;
}
String proxyClientIp = request.getHeader("Proxy-Client-IP");
if (proxyClientIp != null && !proxyClientIp.isEmpty() && !"unknown".equalsIgnoreCase(proxyClientIp)) {
return proxyClientIp;
}
String wlProxyClientIp = request.getHeader("WL-Proxy-Client-IP");
if (wlProxyClientIp != null && !wlProxyClientIp.isEmpty() && !"unknown".equalsIgnoreCase(wlProxyClientIp)) {
return wlProxyClientIp;
}
return request.getRemoteAddr();
}
}

+ 192
- 0
jeecg-boot-module-system/src/main/java/org/jeecg/modules/system/security/IpAccessLimitInterceptor.java View File

@ -0,0 +1,192 @@
package org.jeecg.modules.system.security;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* IP访问限制拦截器
* 用于防止恶意攻击限制单个IP的访问频率
*
* @author system
* @date 2024
*/
@Slf4j
@Component
public class IpAccessLimitInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// Redis key前缀
private static final String IP_ACCESS_COUNT_KEY = "ip_access_count:";
private static final String IP_BLACKLIST_KEY = "ip_blacklist:";
private static final String IP_REQUEST_TIMES_KEY = "ip_request_times:";
// 配置参数
private static final int MAX_REQUESTS_PER_MINUTE = 120; // 每分钟最大请求数
private static final int MAX_REQUESTS_PER_SECOND = 15; // 每秒最大请求数
private static final int BLACKLIST_DURATION_MINUTES = 30; // 黑名单持续时间分钟
private static final int VIOLATION_THRESHOLD = 3; // 违规次数阈值
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String clientIp = getClientIpAddress(request);
// 检查是否在黑名单中
if (isInBlacklist(clientIp)) {
log.warn("IP {} 在黑名单中,拒绝访问", clientIp);
sendErrorResponse(response, "IP已被禁止访问");
return false;
}
// 检查是否为白名单IP可以跳过限制
if (isWhitelistIp(clientIp)) {
return true;
}
// 检查访问频率
if (!checkAccessLimit(clientIp)) {
log.warn("IP {} 访问频率过高,加入黑名单", clientIp);
addToBlacklist(clientIp);
sendErrorResponse(response, "访问频率过高,IP已被暂时禁止");
return false;
}
log.info("IP {} 访问频率正常,继续处理", clientIp);
return true;
}
/**
* 获取客户端真实IP地址
*/
private String getClientIpAddress(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty() && !"unknown".equalsIgnoreCase(xForwardedFor)) {
return xForwardedFor.split(",")[0].trim();
}
String xRealIp = request.getHeader("X-Real-IP");
if (xRealIp != null && !xRealIp.isEmpty() && !"unknown".equalsIgnoreCase(xRealIp)) {
return xRealIp;
}
String proxyClientIp = request.getHeader("Proxy-Client-IP");
if (proxyClientIp != null && !proxyClientIp.isEmpty() && !"unknown".equalsIgnoreCase(proxyClientIp)) {
return proxyClientIp;
}
String wlProxyClientIp = request.getHeader("WL-Proxy-Client-IP");
if (wlProxyClientIp != null && !wlProxyClientIp.isEmpty() && !"unknown".equalsIgnoreCase(wlProxyClientIp)) {
return wlProxyClientIp;
}
return request.getRemoteAddr();
}
/**
* 检查IP是否在黑名单中
*/
private boolean isInBlacklist(String ip) {
String key = IP_BLACKLIST_KEY + ip;
return redisTemplate.hasKey(key);
}
/**
* 检查是否为白名单IP
*/
private boolean isWhitelistIp(String ip) {
// 本地IP和内网IP跳过检查
return ip.equals("127.0.0.1") ||
ip.equals("localhost") ||
ip.startsWith("192.168.") ||
ip.startsWith("10.") ||
ip.startsWith("172.");
}
/**
* 检查访问限制
*/
private boolean checkAccessLimit(String ip) {
long currentTime = System.currentTimeMillis();
long currentSecond = currentTime / 1000;
long currentMinute = currentTime / 60000;
// 检查每秒请求数
String secondKey = IP_ACCESS_COUNT_KEY + ip + ":second:" + currentSecond;
Integer secondCount = (Integer) redisTemplate.opsForValue().get(secondKey);
if (secondCount == null) {
secondCount = 0;
}
if (secondCount >= MAX_REQUESTS_PER_SECOND) {
recordViolation(ip);
return false;
}
// 检查每分钟请求数
String minuteKey = IP_ACCESS_COUNT_KEY + ip + ":minute:" + currentMinute;
Integer minuteCount = (Integer) redisTemplate.opsForValue().get(minuteKey);
if (minuteCount == null) {
minuteCount = 0;
}
if (minuteCount >= MAX_REQUESTS_PER_MINUTE) {
recordViolation(ip);
return false;
}
// 更新计数器
redisTemplate.opsForValue().increment(secondKey);
redisTemplate.expire(secondKey, 2, TimeUnit.SECONDS);
redisTemplate.opsForValue().increment(minuteKey);
redisTemplate.expire(minuteKey, 2, TimeUnit.MINUTES);
return true;
}
/**
* 记录违规行为
*/
private void recordViolation(String ip) {
String violationKey = "ip_violation:" + ip;
Integer violationCount = (Integer) redisTemplate.opsForValue().get(violationKey);
if (violationCount == null) {
violationCount = 0;
}
violationCount++;
redisTemplate.opsForValue().set(violationKey, violationCount, 1, TimeUnit.HOURS);
// 如果违规次数达到阈值加入黑名单
if (violationCount >= VIOLATION_THRESHOLD) {
addToBlacklist(ip);
}
}
/**
* 将IP加入黑名单
*/
private void addToBlacklist(String ip) {
String key = IP_BLACKLIST_KEY + ip;
redisTemplate.opsForValue().set(key, System.currentTimeMillis(), BLACKLIST_DURATION_MINUTES, TimeUnit.MINUTES);
log.warn("IP {} 已被加入黑名单,持续时间: {} 分钟", ip, BLACKLIST_DURATION_MINUTES);
}
/**
* 发送错误响应
*/
private void sendErrorResponse(HttpServletResponse response, String message) throws IOException {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"success\":false,\"message\":\"" + message + "\",\"code\":403}");
}
}

+ 211
- 0
jeecg-boot-module-system/src/main/java/org/jeecg/modules/system/security/IpSecurityService.java View File

@ -0,0 +1,211 @@
package org.jeecg.modules.system.security;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* IP安全服务
* 提供IP黑名单白名单管理功能
*
* @author system
* @date 2024
*/
@Slf4j
@Service
public class IpSecurityService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String IP_BLACKLIST_KEY = "ip_blacklist:";
private static final String IP_WHITELIST_KEY = "ip_whitelist:";
private static final String IP_ACCESS_COUNT_KEY = "ip_access_count:";
/**
* 手动添加IP到黑名单
*/
public void addToBlacklist(String ip, int durationMinutes) {
String key = IP_BLACKLIST_KEY + ip;
redisTemplate.opsForValue().set(key, System.currentTimeMillis(), durationMinutes, TimeUnit.MINUTES);
log.info("手动将IP {} 加入黑名单,持续时间: {} 分钟", ip, durationMinutes);
}
/**
* 从黑名单中移除IP
*/
public void removeFromBlacklist(String ip) {
String key = IP_BLACKLIST_KEY + ip;
redisTemplate.delete(key);
log.info("从黑名单中移除IP: {}", ip);
}
/**
* 添加IP到白名单
*/
public void addToWhitelist(String ip) {
String key = IP_WHITELIST_KEY + ip;
redisTemplate.opsForValue().set(key, System.currentTimeMillis());
log.info("将IP {} 加入白名单", ip);
}
/**
* 从白名单中移除IP
*/
public void removeFromWhitelist(String ip) {
String key = IP_WHITELIST_KEY + ip;
redisTemplate.delete(key);
log.info("从白名单中移除IP: {}", ip);
}
/**
* 检查IP是否在黑名单中
*/
public boolean isInBlacklist(String ip) {
String key = IP_BLACKLIST_KEY + ip;
return redisTemplate.hasKey(key);
}
/**
* 检查IP是否在白名单中
*/
public boolean isInWhitelist(String ip) {
String key = IP_WHITELIST_KEY + ip;
return redisTemplate.hasKey(key);
}
/**
* 获取所有黑名单IP
*/
public List<IpInfo> getBlacklistIps() {
Set<String> keys = redisTemplate.keys(IP_BLACKLIST_KEY + "*");
List<IpInfo> blacklistIps = new ArrayList<>();
if (keys != null) {
for (String key : keys) {
String ip = key.replace(IP_BLACKLIST_KEY, "");
Long addTime = (Long) redisTemplate.opsForValue().get(key);
Long ttl = redisTemplate.getExpire(key);
IpInfo ipInfo = new IpInfo();
ipInfo.setIp(ip);
ipInfo.setAddTime(addTime);
ipInfo.setTtl(ttl);
ipInfo.setType("blacklist");
blacklistIps.add(ipInfo);
}
}
return blacklistIps;
}
/**
* 获取所有白名单IP
*/
public List<IpInfo> getWhitelistIps() {
Set<String> keys = redisTemplate.keys(IP_WHITELIST_KEY + "*");
List<IpInfo> whitelistIps = new ArrayList<>();
if (keys != null) {
for (String key : keys) {
String ip = key.replace(IP_WHITELIST_KEY, "");
Long addTime = (Long) redisTemplate.opsForValue().get(key);
IpInfo ipInfo = new IpInfo();
ipInfo.setIp(ip);
ipInfo.setAddTime(addTime);
ipInfo.setTtl(-1L); // 白名单永不过期
ipInfo.setType("whitelist");
whitelistIps.add(ipInfo);
}
}
return whitelistIps;
}
/**
* 获取IP访问统计信息
*/
public IpAccessStats getIpAccessStats(String ip) {
long currentTime = System.currentTimeMillis();
long currentSecond = currentTime / 1000;
long currentMinute = currentTime / 60000;
String secondKey = IP_ACCESS_COUNT_KEY + ip + ":second:" + currentSecond;
String minuteKey = IP_ACCESS_COUNT_KEY + ip + ":minute:" + currentMinute;
Integer secondCount = (Integer) redisTemplate.opsForValue().get(secondKey);
Integer minuteCount = (Integer) redisTemplate.opsForValue().get(minuteKey);
IpAccessStats stats = new IpAccessStats();
stats.setIp(ip);
stats.setCurrentSecondCount(secondCount != null ? secondCount : 0);
stats.setCurrentMinuteCount(minuteCount != null ? minuteCount : 0);
stats.setInBlacklist(isInBlacklist(ip));
stats.setInWhitelist(isInWhitelist(ip));
return stats;
}
/**
* 清除IP的所有访问记录
*/
public void clearIpAccessRecords(String ip) {
Set<String> keys = redisTemplate.keys(IP_ACCESS_COUNT_KEY + ip + "*");
if (keys != null && !keys.isEmpty()) {
redisTemplate.delete(keys);
log.info("清除IP {} 的访问记录", ip);
}
}
/**
* IP信息实体类
*/
public static class IpInfo {
private String ip;
private Long addTime;
private Long ttl;
private String type;
// getters and setters
public String getIp() { return ip; }
public void setIp(String ip) { this.ip = ip; }
public Long getAddTime() { return addTime; }
public void setAddTime(Long addTime) { this.addTime = addTime; }
public Long getTtl() { return ttl; }
public void setTtl(Long ttl) { this.ttl = ttl; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
}
/**
* IP访问统计实体类
*/
public static class IpAccessStats {
private String ip;
private Integer currentSecondCount;
private Integer currentMinuteCount;
private Boolean inBlacklist;
private Boolean inWhitelist;
// getters and setters
public String getIp() { return ip; }
public void setIp(String ip) { this.ip = ip; }
public Integer getCurrentSecondCount() { return currentSecondCount; }
public void setCurrentSecondCount(Integer currentSecondCount) { this.currentSecondCount = currentSecondCount; }
public Integer getCurrentMinuteCount() { return currentMinuteCount; }
public void setCurrentMinuteCount(Integer currentMinuteCount) { this.currentMinuteCount = currentMinuteCount; }
public Boolean getInBlacklist() { return inBlacklist; }
public void setInBlacklist(Boolean inBlacklist) { this.inBlacklist = inBlacklist; }
public Boolean getInWhitelist() { return inWhitelist; }
public void setInWhitelist(Boolean inWhitelist) { this.inWhitelist = inWhitelist; }
}
}

+ 39
- 0
jeecg-boot-module-system/src/main/java/org/jeecg/modules/system/security/WebSecurityConfig.java View File

@ -0,0 +1,39 @@
package org.jeecg.modules.system.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Web安全配置
* 注册IP访问限制拦截器
*
* @author system
* @date 2024
*/
@Configuration
public class WebSecurityConfig implements WebMvcConfigurer {
@Autowired
private IpAccessLimitInterceptor ipAccessLimitInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(ipAccessLimitInterceptor)
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns(
"/sys/login", // 排除登录接口
"/sys/logout", // 排除登出接口
"/sys/randomImage", // 排除验证码接口
"/sys/checkCaptcha", // 排除验证码校验接口
"/sys/ip/manage/**", // 排除IP管理接口避免管理员被锁定
"/error", // 排除错误页面
"/favicon.ico", // 排除图标
"/static/**", // 排除静态资源
"/public/**", // 排除公共资源
"/actuator/**" // 排除监控端点
)
.order(1); // 设置拦截器优先级
}
}

Loading…
Cancel
Save