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

JSF 2.3应用实现登录失败X次后用户锁定的方案问询

实现连续登录失败锁定与自动解锁方案(JSF 2.3 + Liberty + LDAP)

由于j_security_check是Liberty容器内置的认证端点,无法直接扩展失败次数统计逻辑,需通过自定义登录拦截+容器认证触发的方式实现需求,以下是具体步骤:


1. 调整登录表单提交逻辑

修改login.xhtml的表单提交目标,从直接提交到j_security_check改为指向自定义托管Bean的登录方法:

<h:form id="loginForm">
    <h:inputText id="username" value="#{loginBean.username}" required="true"/>
    <h:inputSecret id="password" value="#{loginBean.password}" required="true"/>
    <h:commandButton value="登录" action="#{loginBean.login}"/>
    <h:messages globalOnly="true" styleClass="error"/>
</h:form>

2. 实现失败次数存储方案

使用Liberty内置的分布式缓存存储用户失败登录记录(集群环境友好),单节点也可改用数据库或Session存储:

2.1 Liberty服务器配置(server.xml)

启用缓存特性并定义缓存实例:

<featureManager>
    <feature>webProfile-8.0</feature> <!-- 适配JSF 2.3的Web特性 -->
    <feature>ldapRegistry-3.0</feature> <!-- 保留原有LDAP配置 -->
    <feature>cache-1.0</feature>
</featureManager>

<!-- 定义登录失败记录缓存 -->
<cache id="loginFailureCache" jndiName="cache/loginFailureCache"/>

2.2 失败记录实体类

import java.io.Serializable;
import java.time.LocalDateTime;

public class LoginFailureRecord implements Serializable {
    private int failCount;
    private LocalDateTime lockUntil; // 锁定截止时间

    public LoginFailureRecord(int failCount, LocalDateTime lockUntil) {
        this.failCount = failCount;
        this.lockUntil = lockUntil;
    }

    // getter/setter 省略
}

3. 自定义登录逻辑(托管Bean)

通过request.login()触发Liberty的LDAP认证,同时在认证前后处理失败次数统计与锁定逻辑:

import javax.enterprise.context.RequestScoped;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.inject.Named;
import javax.resource.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.cache.Cache;
import java.time.LocalDateTime;

@Named
@RequestScoped
public class LoginBean {
    @Resource(lookup = "cache/loginFailureCache")
    private Cache<String, LoginFailureRecord> loginFailureCache;

    @Inject
    private HttpServletRequest request;
    @Inject
    private HttpServletResponse response;

    private String username;
    private String password;

    // getter/setter 省略

    public String login() {
        // 1. 检查用户是否处于锁定状态
        LoginFailureRecord record = loginFailureCache.get(username);
        if (record != null && record.getLockUntil() != null && record.getLockUntil().isAfter(LocalDateTime.now())) {
            long remainingMinutes = java.time.Duration.between(LocalDateTime.now(), record.getLockUntil()).toMinutes();
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_ERROR, 
                    "账户已锁定,剩余解锁时间:" + remainingMinutes + "分钟", null));
            return "errorlogin.xhtml?faces-redirect=true";
        }

        // 2. 触发LDAP认证
        try {
            request.login(username, password);
            // 认证成功:清除失败记录
            loginFailureCache.remove(username);
            // 跳转至原请求页面或首页
            String originalUrl = request.getParameter("redirect");
            return originalUrl != null ? originalUrl + "?faces-redirect=true" : "/home.xhtml?faces-redirect=true";
        } catch (Exception e) {
            // 3. 认证失败:更新失败次数与锁定状态
            int newFailCount = record != null ? record.getFailCount() + 1 : 1;
            LocalDateTime lockUntil = null;
            if (newFailCount >= 3) {
                lockUntil = LocalDateTime.now().plusMinutes(10); // 配置X分钟锁定,此处为10分钟示例
            }
            loginFailureCache.put(username, new LoginFailureRecord(newFailCount, lockUntil));

            // 提示剩余尝试次数
            int remainingAttempts = 3 - newFailCount;
            FacesContext.getCurrentInstance().addMessage(null, 
                new FacesMessage(FacesMessage.SEVERITY_ERROR, 
                    "用户名或密码错误,剩余尝试次数:" + (remainingAttempts >=0 ? remainingAttempts : 0), null));
            return "errorlogin.xhtml?faces-redirect=true";
        }
    }
}

4. 错误页调整(errorlogin.xhtml)

保留原有错误页,确保能显示托管Bean传递的错误信息:

<h:messages globalOnly="true" styleClass="error-message"/>
<h:link value="返回登录页" outcome="login.xhtml"/>

5. 自动解锁逻辑说明

每次登录前会检查缓存中存储的lockUntil时间:

  • 若当前时间超过锁定截止时间,自动跳过锁定检查,允许用户重新尝试登录
  • 若用户在锁定期间尝试登录,会显示剩余解锁时间

关键注意事项

  • 集群环境必须使用分布式缓存(如Liberty的cache-1.0特性),避免节点间状态不一致
  • 若需持久化失败记录(服务器重启不丢失),可将缓存配置为持久化至数据库
  • 原有LDAP认证配置无需修改,request.login()会自动触发Liberty的LDAP验证流程

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 14:45:47