feat: 增强安全组件并添加内存保护机制

1. 重构 ReplayAttackInterceptor、RateLimitInterceptor、CaptchaUtil 和 LoginLockUtil,使用 LRUCache 和读写锁优化内存管理
2. 新增 MemoryProtector 类实现内存监控和保护机制
3. 为所有内存缓存组件添加容量限制和过期清理策略
4. 更新 .gitignore 文件配置
This commit is contained in:
ikmkj
2026-03-03 18:23:28 +08:00
parent 99e44e6c3b
commit 61aeba9c65
6 changed files with 488 additions and 100 deletions

View File

@@ -1,13 +1,18 @@
package com.test.bijihoudaun.util;
import lombok.extern.slf4j.Slf4j;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.ConcurrentHashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 登录锁定工具类
* 使用本地内存存储登录失败记录
* 使用本地内存存储登录失败记录,带容量限制
*/
@Slf4j
public class LoginLockUtil {
// 最大失败次数
@@ -16,9 +21,12 @@ public class LoginLockUtil {
private static final int LOCK_TIME_MINUTES = 30;
// 失败记录过期时间(分钟)
private static final int RECORD_EXPIRE_MINUTES = 60;
// 最大存储记录数(防止内存溢出)
private static final int MAX_RECORDS = 10000;
// 登录失败记录key=用户名value=失败记录
private static final ConcurrentHashMap<String, LoginAttempt> attempts = new ConcurrentHashMap<>();
private static final LRUCache<String, LoginAttempt> attempts = new LRUCache<>(MAX_RECORDS);
private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private static class LoginAttempt {
int failedCount;
@@ -32,6 +40,27 @@ public class LoginLockUtil {
}
}
/**
* 简单的 LRU 缓存实现
*/
private static class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int maxSize;
LRUCache(int maxSize) {
super(maxSize, 0.75f, true);
this.maxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
boolean shouldRemove = size() > maxSize;
if (shouldRemove) {
log.warn("登录锁定记录达到上限,移除最旧的记录: {}", eldest.getKey());
}
return shouldRemove;
}
}
/**
* 记录登录失败
* @param username 用户名
@@ -41,13 +70,19 @@ public class LoginLockUtil {
cleanupExpiredRecords();
LoginAttempt attempt = attempts.computeIfAbsent(username, k -> new LoginAttempt());
attempt.failedCount++;
attempt.lastAttemptTime = LocalDateTime.now();
lock.writeLock().lock();
try {
LoginAttempt attempt = attempts.computeIfAbsent(username, k -> new LoginAttempt());
attempt.failedCount++;
attempt.lastAttemptTime = LocalDateTime.now();
// 达到最大失败次数,锁定账号
if (attempt.failedCount >= MAX_FAILED_ATTEMPTS) {
attempt.lockUntil = LocalDateTime.now().plusMinutes(LOCK_TIME_MINUTES);
// 达到最大失败次数,锁定账号
if (attempt.failedCount >= MAX_FAILED_ATTEMPTS) {
attempt.lockUntil = LocalDateTime.now().plusMinutes(LOCK_TIME_MINUTES);
log.warn("账号 [{}] 已被锁定 {} 分钟", username, LOCK_TIME_MINUTES);
}
} finally {
lock.writeLock().unlock();
}
}
@@ -57,7 +92,13 @@ public class LoginLockUtil {
*/
public static void recordSuccess(String username) {
if (username == null || username.isEmpty()) return;
attempts.remove(username);
lock.writeLock().lock();
try {
attempts.remove(username);
} finally {
lock.writeLock().unlock();
}
}
/**
@@ -68,20 +109,32 @@ public class LoginLockUtil {
public static boolean isLocked(String username) {
if (username == null || username.isEmpty()) return false;
LoginAttempt attempt = attempts.get(username);
if (attempt == null) return false;
lock.readLock().lock();
try {
LoginAttempt attempt = attempts.get(username);
if (attempt == null) return false;
// 检查是否仍在锁定时间内
if (attempt.lockUntil != null) {
if (LocalDateTime.now().isBefore(attempt.lockUntil)) {
return true;
} else {
// 锁定时间已过,清除记录
attempts.remove(username);
return false;
// 检查是否仍在锁定时间内
if (attempt.lockUntil != null) {
if (LocalDateTime.now().isBefore(attempt.lockUntil)) {
return true;
} else {
// 锁定时间已过,清除记录
lock.readLock().unlock();
lock.writeLock().lock();
try {
attempts.remove(username);
} finally {
lock.writeLock().unlock();
lock.readLock().lock();
}
return false;
}
}
return false;
} finally {
lock.readLock().unlock();
}
return false;
}
/**
@@ -92,11 +145,16 @@ public class LoginLockUtil {
public static long getRemainingLockTime(String username) {
if (username == null || username.isEmpty()) return 0;
LoginAttempt attempt = attempts.get(username);
if (attempt == null || attempt.lockUntil == null) return 0;
lock.readLock().lock();
try {
LoginAttempt attempt = attempts.get(username);
if (attempt == null || attempt.lockUntil == null) return 0;
long remaining = ChronoUnit.SECONDS.between(LocalDateTime.now(), attempt.lockUntil);
return Math.max(0, remaining);
long remaining = ChronoUnit.SECONDS.between(LocalDateTime.now(), attempt.lockUntil);
return Math.max(0, remaining);
} finally {
lock.readLock().unlock();
}
}
/**
@@ -107,25 +165,52 @@ public class LoginLockUtil {
public static int getRemainingAttempts(String username) {
if (username == null || username.isEmpty()) return MAX_FAILED_ATTEMPTS;
LoginAttempt attempt = attempts.get(username);
if (attempt == null) return MAX_FAILED_ATTEMPTS;
lock.readLock().lock();
try {
LoginAttempt attempt = attempts.get(username);
if (attempt == null) return MAX_FAILED_ATTEMPTS;
return Math.max(0, MAX_FAILED_ATTEMPTS - attempt.failedCount);
return Math.max(0, MAX_FAILED_ATTEMPTS - attempt.failedCount);
} finally {
lock.readLock().unlock();
}
}
/**
* 清理过期记录
*/
private static void cleanupExpiredRecords() {
LocalDateTime now = LocalDateTime.now();
attempts.entrySet().removeIf(entry -> {
LoginAttempt attempt = entry.getValue();
// 未锁定且长时间没有登录的记录
if (attempt.lockUntil == null) {
return ChronoUnit.MINUTES.between(attempt.lastAttemptTime, now) > RECORD_EXPIRE_MINUTES;
}
// 锁定已过期的记录
return now.isAfter(attempt.lockUntil);
});
// 每100次操作清理一次减少开销
if (attempts.size() < MAX_RECORDS * 0.8) {
return;
}
lock.writeLock().lock();
try {
LocalDateTime now = LocalDateTime.now();
attempts.entrySet().removeIf(entry -> {
LoginAttempt attempt = entry.getValue();
// 未锁定且长时间没有登录的记录
if (attempt.lockUntil == null) {
return ChronoUnit.MINUTES.between(attempt.lastAttemptTime, now) > RECORD_EXPIRE_MINUTES;
}
// 锁定已过期的记录
return now.isAfter(attempt.lockUntil);
});
} finally {
lock.writeLock().unlock();
}
}
/**
* 获取当前记录数量(用于监控)
*/
public static int getRecordCount() {
lock.readLock().lock();
try {
return attempts.size();
} finally {
lock.readLock().unlock();
}
}
}