老项目无痛添加用户登录限制-密码错误5次锁定用户十分钟

80 阅读3分钟

应用场景

临时接到一个任务,有一个特别老的项目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方法,登录前校验一下用户名是否存在即可。

image.png

image.png

最后发版本的时候需要注意创建一个本地txt文件。当然条件允许可以操作数据库的话就不用持久化到txt里了。