feat: 添加用户角色字段并实现权限控制

fix(security): 修复重放攻击拦截器的时间戳验证漏洞

refactor(security): 重构验证码工具类使用线程安全实现

perf(login): 优化登录锁定工具类性能并添加定期清理

fix(editor): 修复笔记编辑器空指针问题

style: 清理数据库索引脚本中的冗余注释

fix(api): 修复前端API调用参数编码问题

feat(image): 实现图片名称同步服务

refactor(markdown): 重构Markdown服务分离图片名称同步逻辑

fix(xss): 添加HTML转义函数防止XSS攻击

fix(user): 修复用户服务权限加载问题

fix(rate-limit): 修复速率限制拦截器并发问题

fix(axios): 生产环境隐藏详细错误信息

fix(image): 修复图片上传和删除的权限验证

refactor(captcha): 重构验证码工具类使用并发安全实现

fix(jwt): 修复JWT过滤器空指针问题

fix(export): 修复笔记导出XSS漏洞

fix(search): 修复Markdown搜索SQL注入问题

fix(interceptor): 修复重放攻击拦截器逻辑错误

fix(controller): 修复用户控制器空指针问题

fix(security): 修复nonce生成使用密码学安全方法
This commit is contained in:
ikmkj
2026-03-03 20:48:40 +08:00
parent d54719d82d
commit 375ccb89ff
22 changed files with 512 additions and 359 deletions

View File

@@ -6,11 +6,14 @@ import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 登录锁定工具类
* 使用本地内存存储登录失败记录,带容量限制
* 使用本地内存存储登录失败记录,带容量限制和定期清理
*/
@Slf4j
public class LoginLockUtil {
@@ -28,6 +31,19 @@ public class LoginLockUtil {
private static final LRUCache<String, LoginAttempt> attempts = new LRUCache<>(MAX_RECORDS);
private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
// 定期清理线程池
private static final ScheduledExecutorService cleanupScheduler = Executors.newSingleThreadScheduledExecutor(r -> {
Thread t = new Thread(r, "login-lock-cleanup");
t.setDaemon(true);
return t;
});
static {
// 每10分钟清理一次过期记录
cleanupScheduler.scheduleAtFixedRate(LoginLockUtil::cleanupExpiredLocks,
10, 10, TimeUnit.MINUTES);
}
private static class LoginAttempt {
int failedCount;
LocalDateTime lastAttemptTime;
@@ -38,6 +54,10 @@ public class LoginLockUtil {
this.lastAttemptTime = LocalDateTime.now();
this.lockUntil = null;
}
boolean isExpired() {
return ChronoUnit.MINUTES.between(lastAttemptTime, LocalDateTime.now()) > RECORD_EXPIRE_MINUTES;
}
}
/**
@@ -68,8 +88,6 @@ public class LoginLockUtil {
public static void recordFailedAttempt(String username) {
if (username == null || username.isEmpty()) return;
cleanupExpiredRecords();
lock.writeLock().lock();
try {
LoginAttempt attempt = attempts.computeIfAbsent(username, k -> new LoginAttempt());
@@ -116,20 +134,7 @@ public class LoginLockUtil {
// 检查是否仍在锁定时间内
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 LocalDateTime.now().isBefore(attempt.lockUntil);
}
return false;
} finally {
@@ -177,14 +182,9 @@ public class LoginLockUtil {
}
/**
* 清理过期记录
* 清理过期的锁定记录
*/
private static void cleanupExpiredRecords() {
// 每100次操作清理一次减少开销
if (attempts.size() < MAX_RECORDS * 0.8) {
return;
}
private static void cleanupExpiredLocks() {
lock.writeLock().lock();
try {
LocalDateTime now = LocalDateTime.now();
@@ -192,7 +192,7 @@ public class LoginLockUtil {
LoginAttempt attempt = entry.getValue();
// 未锁定且长时间没有登录的记录
if (attempt.lockUntil == null) {
return ChronoUnit.MINUTES.between(attempt.lastAttemptTime, now) > RECORD_EXPIRE_MINUTES;
return attempt.isExpired();
}
// 锁定已过期的记录
return now.isAfter(attempt.lockUntil);