feat: 增强安全组件并添加内存保护机制
1. 重构 ReplayAttackInterceptor、RateLimitInterceptor、CaptchaUtil 和 LoginLockUtil,使用 LRUCache 和读写锁优化内存管理 2. 新增 MemoryProtector 类实现内存监控和保护机制 3. 为所有内存缓存组件添加容量限制和过期清理策略 4. 更新 .gitignore 文件配置
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user