Files
biji/biji-houdaun/src/main/java/com/test/bijihoudaun/util/LoginLockUtil.java
ikmkj 23929a974f feat(安全): 添加验证码和登录安全增强功能
新增验证码功能用于敏感操作,包括删除账号、修改密码等
添加登录失败锁定机制和限流策略
实现防重放攻击和XSS防护增强
重构XSS拦截器使用请求包装器
2026-03-03 17:49:50 +08:00

132 lines
4.1 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
});
}
}