You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java Web Servlet实现登录失败3次临时锁定功能的方案咨询

连续错误锁定功能实现方案

现有代码的核心问题

  1. 你当前定义的attempts是方法局部变量,每次请求都会重置为3,完全无法累计用户的历史错误次数
  2. 账号校验逻辑有误:三个if并行执行,即使用户输入的是第一组合法账号,后面两个if的else分支仍会触发attempts减1,计数逻辑完全错误
  3. 没有存储锁定到期时间的逻辑,无法判断用户是否处于锁定状态

实现思路

我们需要两个全局线程安全集合分别存储:

  • 每个邮箱的累计错误次数
  • 每个邮箱的锁定到期时间戳
    收到登录请求后优先判断当前邮箱是否处于锁定状态,未锁定再做密码校验,错误后累计次数,次数达到阈值就设置锁定时间。

修改后的完整代码

import java.io.*;
import java.time.Instant;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;

@WebServlet("/HelloServlet")
public class HelloServlet extends HttpServlet {
    // 存储合法账号密码 替换原来的三个并行if
    private static final Map<String, String> VALID_ACCOUNTS = new ConcurrentHashMap<>();
    // 存储每个邮箱的错误尝试次数
    private static final Map<String, Integer> LOGIN_ATTEMPTS = new ConcurrentHashMap<>();
    // 存储每个邮箱的锁定到期时间戳(毫秒)
    private static final Map<String, Long> LOCK_EXPIRE_TIME = new ConcurrentHashMap<>();
    // 最大允许错误次数
    private static final int MAX_ATTEMPTS = 3;
    // 锁定时长 单位:毫秒 这里设为10分钟 可自行修改
    private static final long LOCK_DURATION = 10 * 60 * 1000;

    // 初始化合法账号
    @Override
    public void init() {
        VALID_ACCOUNTS.put("Mhamdoon4@gmail.com", "pass001");
        VALID_ACCOUNTS.put("Mhamdoon6@gmail.com", "pass0011");
        VALID_ACCOUNTS.put("Mhamdoon12@gmail.com", "pass00901");
    }

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/html;charset=utf-8");
        String email = request.getParameter("email");
        String password = request.getParameter("password");
        PrintWriter printWriter = response.getWriter();

        try {
            // 第一步:先判断邮箱是否被锁定
            if (LOCK_EXPIRE_TIME.containsKey(email)) {
                long expireTime = LOCK_EXPIRE_TIME.get(email);
                long now = Instant.now().toEpochMilli();
                if (now < expireTime) {
                    long leftMinutes = (expireTime - now) / 1000 / 60;
                    printWriter.println("该账号已被锁定,请" + leftMinutes + "分钟后再尝试");
                    return;
                } else {
                    // 锁定到期 清除锁定记录和错误次数
                    LOCK_EXPIRE_TIME.remove(email);
                    LOGIN_ATTEMPTS.remove(email);
                }
            }

            // 第二步:校验账号密码是否合法
            if (VALID_ACCOUNTS.containsKey(email) && VALID_ACCOUNTS.get(email).equals(password)) {
                // 登录成功 清除该账号的错误次数记录
                LOGIN_ATTEMPTS.remove(email);
                HttpSession session = request.getSession();
                session.setAttribute("loginUser", email);
                request.setAttribute("email", email);
                request.getRequestDispatcher("welcome.jsp").forward(request, response);
                return;
            }

            // 第三步:登录失败 累计错误次数
            int currentAttempts = LOGIN_ATTEMPTS.getOrDefault(email, 0) + 1;
            LOGIN_ATTEMPTS.put(email, currentAttempts);
            int leftAttempts = MAX_ATTEMPTS - currentAttempts;

            if (currentAttempts >= MAX_ATTEMPTS) {
                // 达到最大错误次数 设置锁定时间
                long expireTime = Instant.now().toEpochMilli() + LOCK_DURATION;
                LOCK_EXPIRE_TIME.put(email, expireTime);
                printWriter.println("连续3次输入错误,账号已被锁定10分钟");
            } else {
                printWriter.println("邮箱或密码错误,剩余尝试次数:" + leftAttempts);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void destroy() {
    }
}

补充说明

  • 这里用ConcurrentHashMap是因为Servlet是单实例多线程运行,线程安全集合可以避免并发场景下的计数错误
  • 如果你需要服务重启后锁定记录仍然有效,可以把错误次数和锁定时间存在数据库或者Redis中
  • 登录请求建议用POST方法而不是GET,避免密码出现在URL日志中,安全性更高

内容的提问来源于stack exchange,提问作者Mohammed Hamdoon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 17:06:08