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
相关产品推荐
相关产品推荐

