应用场景
临时接到一个任务,有一个特别老的项目18-19年左右的项目,被扫描出了漏洞,需要追加一个登录的限制。
研究半天老系统的框架servlet,hibernate,tomcat,war包淦老古董项目啊。只能打补丁升级了改完代码编译后把class文件替换了升级。
本来想着搞个map实现一下结果map初始化不到servlet容器里,导致失败计数永远为0 ,难搞。
最后用了本地txt文件的方式做了一个持久化,记录了一下用户名失败次数失败时间等信息。
核心代码
import java.io.*;
import java.util.HashMap;
import java.util.Map;
public class LoginManager {
private Map<String, Integer> failedLoginAttempts = new HashMap<>(); // 存储用户失败登录次数的映射
private Map<String, Long> lockedUsers = new HashMap<>(); // 存储被锁定用户的映射
private static final int MAX_FAILED_ATTEMPTS = 5; // 最大失败尝试次数
private static final long LOCK_TIME_MILLIS = 10 * 60 * 1000; // 锁定时间,10分钟(毫秒为单位)
private static final String FILE_PATH = "d:/login_info.txt"; // 存储登录信息的文件路径
public LoginManager() {
loadLoginInfoFromFile(); // 构造函数中加载登录信息
}
// 登录成功时调用的方法
public synchronized void loginSuccess(String username) {
failedLoginAttempts.remove(username); // 移除失败登录记录
lockedUsers.remove(username); // 移除锁定用户记录
saveLoginInfoToFile(); // 保存更新后的登录信息到文件
}
// 登录失败时调用的方法
public synchronized void loginFailed(String username) {
if (lockedUsers.containsKey(username)) {
return; // 如果用户已经被锁定,直接返回
}
int failedAttempts = failedLoginAttempts.getOrDefault(username, 0); // 获取当前用户失败登录次数
failedAttempts++;
failedLoginAttempts.put(username, failedAttempts); // 更新失败登录次数
if (failedAttempts >= MAX_FAILED_ATTEMPTS) { // 如果失败次数达到阈值,锁定用户
lockedUsers.put(username, System.currentTimeMillis() + LOCK_TIME_MILLIS);
}
saveLoginInfoToFile(); // 保存更新后的登录信息到文件
}
// 判断用户是否被锁定
public synchronized boolean isUserLocked(String username) {
// 判断用户是否存在且锁定时间未过期
return lockedUsers.containsKey(username) && lockedUsers.get(username) > System.currentTimeMillis();
}
// 从文件中加载登录信息
private void loadLoginInfoFromFile() {
try (BufferedReader reader = new BufferedReader(new FileReader(FILE_PATH))) {
String line;
while ((line = reader.readLine()) != null) {
String[] parts = line.split(",");
if (parts.length == 3) {
String username = parts[0];
int failedAttempts = Integer.parseInt(parts[1]);
long lockTime = Long.parseLong(parts[2]);
failedLoginAttempts.put(username, failedAttempts);
if (lockTime > System.currentTimeMillis()) {
lockedUsers.put(username, lockTime);
}
}
}
} catch (IOException | NumberFormatException e) {
e.printStackTrace();
}
}
// 将登录信息保存到文件
private void saveLoginInfoToFile() {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(FILE_PATH))) {
for (Map.Entry<String, Integer> entry : failedLoginAttempts.entrySet()) {
String username = entry.getKey();
int failedAttempts = entry.getValue();
long lockTime = lockedUsers.getOrDefault(username, 0L); // 获取用户的锁定时间戳
writer.write(username + "," + failedAttempts + "," + lockTime); // 将锁定时间戳也存储到文件中
writer.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
测试类
public class LoginManagerTest {
public static void main(String[] args) {
LoginManager loginManager = new LoginManager();
// 模拟用户登录过程
String username = "user1";
// 用户登录失败5次,被锁定
for (int i = 0; i < 5; i++) {
loginManager.loginFailed(username);
}
// 检查用户是否被锁定
System.out.println("是否被锁定: " + loginManager.isUserLocked(username));
// 用户登录成功
// loginManager.loginSuccess(username);
// 检查用户是否解锁
System.out.println("是否被锁定: " + loginManager.isUserLocked(username));
username = "user2";
// 用户登录失败5次,被锁定
for (int i = 0; i < 5; i++) {
loginManager.loginFailed(username);
}
// try {
// Thread.sleep(10 * 60 * 1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// 检查用户是否被锁定
System.out.println("是否被锁定: " + loginManager.isUserLocked(username));
// 用户登录成功
loginManager.loginSuccess(username);
// 检查用户是否解锁
System.out.println("是否被锁定: " + loginManager.isUserLocked(username));
}
}
使用逻辑
登录密码失败的时候调用一下loginFailed方法,成功的时候调用一下loginSuccess方法,登录前校验一下用户名是否存在即可。
最后发版本的时候需要注意创建一个本地txt文件。当然条件允许可以操作数据库的话就不用持久化到txt里了。