Java Web Servlet实现登录失败3次临时锁定功能的方案咨询
连续错误锁定功能实现方案
现有代码的核心问题
- 你当前定义的
attempts是方法局部变量,每次请求都会重置为3,完全无法累计用户的历史错误次数 - 账号校验逻辑有误:三个
if并行执行,即使用户输入的是第一组合法账号,后面两个if的else分支仍会触发attempts减1,计数逻辑完全错误 - 没有存储锁定到期时间的逻辑,无法判断用户是否处于锁定状态
实现思路
我们需要两个全局线程安全集合分别存储:
- 每个邮箱的累计错误次数
- 每个邮箱的锁定到期时间戳
收到登录请求后优先判断当前邮箱是否处于锁定状态,未锁定再做密码校验,错误后累计次数,次数达到阈值就设置锁定时间。
修改后的完整代码
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
相关产品推荐
相关产品推荐

