feat(安全): 添加验证码和登录安全增强功能

新增验证码功能用于敏感操作,包括删除账号、修改密码等
添加登录失败锁定机制和限流策略
实现防重放攻击和XSS防护增强
重构XSS拦截器使用请求包装器
This commit is contained in:
ikmkj
2026-03-03 17:49:50 +08:00
parent 5a24569ebd
commit 23929a974f
13 changed files with 763 additions and 26 deletions

View File

@@ -0,0 +1,131 @@
package com.test.bijihoudaun.util;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.ConcurrentHashMap;
/**
* 登录锁定工具类
* 使用本地内存存储登录失败记录
*/
public class LoginLockUtil {
// 最大失败次数
private static final int MAX_FAILED_ATTEMPTS = 5;
// 锁定时间(分钟)
private static final int LOCK_TIME_MINUTES = 30;
// 失败记录过期时间(分钟)
private static final int RECORD_EXPIRE_MINUTES = 60;
// 登录失败记录key=用户名value=失败记录
private static final ConcurrentHashMap<String, LoginAttempt> attempts = new ConcurrentHashMap<>();
private static class LoginAttempt {
int failedCount;
LocalDateTime lastAttemptTime;
LocalDateTime lockUntil;
LoginAttempt() {
this.failedCount = 0;
this.lastAttemptTime = LocalDateTime.now();
this.lockUntil = null;
}
}
/**
* 记录登录失败
* @param username 用户名
*/
public static void recordFailedAttempt(String username) {
if (username == null || username.isEmpty()) return;
cleanupExpiredRecords();
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);
}
}
/**
* 记录登录成功(清除失败记录)
* @param username 用户名
*/
public static void recordSuccess(String username) {
if (username == null || username.isEmpty()) return;
attempts.remove(username);
}
/**
* 检查账号是否被锁定
* @param username 用户名
* @return true-已锁定false-未锁定
*/
public static boolean isLocked(String username) {
if (username == null || username.isEmpty()) return false;
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;
}
}
return false;
}
/**
* 获取剩余锁定时间(秒)
* @param username 用户名
* @return 剩余秒数如果未锁定返回0
*/
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;
long remaining = ChronoUnit.SECONDS.between(LocalDateTime.now(), attempt.lockUntil);
return Math.max(0, remaining);
}
/**
* 获取剩余失败次数
* @param username 用户名
* @return 剩余次数
*/
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;
return Math.max(0, MAX_FAILED_ATTEMPTS - attempt.failedCount);
}
/**
* 清理过期记录
*/
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);
});
}
}