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

Wildfly 16远程EJB登录失败次数限制方案咨询

实现Wildfly 16 EJB安全的登录失败封禁功能

首先明确:Wildfly 16内置的数据库登录模块(不管是Legacy的PicketBox还是默认的Elytron实现)没有直接通过配置就能实现用户级失败次数限制+临时封禁的功能,得通过自定义扩展或者其他方案来实现。下面是几个可行的方案,其中自定义登录模块是最直接且符合你需求的方式:

方案一:自定义登录模块(推荐)

你提到的“维护用户ID与失败登录次数的内存映射”完全可行,甚至可以扩展为持久化存储来避免服务器重启丢失数据。具体实现步骤如下:

1. 编写自定义登录模块类

继承Wildfly的DatabaseServerLoginModule(Legacy安全域)或者Elytron的JdbcSecurityRealm相关类(如果你用Elytron),这里以Legacy的PicketBox为例:

import org.jboss.security.auth.spi.DatabaseServerLoginModule;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

public class BanningDatabaseLoginModule extends DatabaseServerLoginModule {
    // 内存存储:用户名 -> [失败次数, 最后失败时间戳, 封禁截止时间戳]
    private static final ConcurrentHashMap<String, long[]> FAILED_LOGINS = new ConcurrentHashMap<>();
    // 配置参数:允许的最大失败次数,默认5次
    private int maxFailedAttempts = 5;
    // 封禁时长,默认5分钟(毫秒)
    private long banDuration = TimeUnit.MINUTES.toMillis(5);

    @Override
    public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
        super.initialize(subject, callbackHandler, sharedState, options);
        // 读取配置的参数
        if (options.containsKey("maxFailedAttempts")) {
            maxFailedAttempts = Integer.parseInt(options.get("maxFailedAttempts").toString());
        }
        if (options.containsKey("banDuration")) {
            banDuration = Long.parseLong(options.get("banDuration").toString());
        }
    }

    @Override
    protected boolean validatePassword(String inputPassword, String expectedPassword) {
        String username = getUsername();
        
        // 先检查用户是否处于封禁期
        long[] userData = FAILED_LOGINS.get(username);
        if (userData != null && System.currentTimeMillis() < userData[2]) {
            // 仍在封禁期,直接返回验证失败
            return false;
        }

        // 调用父类方法验证密码
        boolean isValid = super.validatePassword(inputPassword, expectedPassword);
        
        if (!isValid) {
            // 密码验证失败,更新失败次数和时间
            handleFailedLogin(username);
        } else {
            // 验证成功,重置失败记录
            FAILED_LOGINS.remove(username);
        }
        
        return isValid;
    }

    private void handleFailedLogin(String username) {
        long[] userData = FAILED_LOGINS.getOrDefault(username, new long[]{0, 0, 0});
        userData[0]++; // 失败次数+1
        userData[1] = System.currentTimeMillis(); // 更新最后失败时间
        
        if (userData[0] >= maxFailedAttempts) {
            // 达到阈值,设置封禁截止时间
            userData[2] = System.currentTimeMillis() + banDuration;
        }
        
        FAILED_LOGINS.put(username, userData);
    }
}

2. 部署自定义模块到Wildfly

将编译好的类打包成JAR,放到wildfly-16.0.0.Final/modules/com/yourcompany/security/main/目录下,同时创建module.xml:

<?xml version="1.0" encoding="UTF-8"?>
<module name="com.yourcompany.security" xmlns="urn:jboss:module:1.3">
    <resources>
        <resource-root path="your-security-module.jar"/>
    </resources>
    <dependencies>
        <module name="org.jboss.security"/>
        <module name="javax.api"/>
    </dependencies>
</module>

3. 配置Wildfly安全域

修改standalone.xml(或domain.xml)的安全域配置,替换原来的DatabaseServerLoginModule为你的自定义模块:

<security-domain name="yourEjbSecurityDomain" cache-type="default">
    <authentication>
        <login-module code="com.yourcompany.security.BanningDatabaseLoginModule" flag="required">
            <!-- 原数据库登录模块的参数 -->
            <module-option name="dsJndiName" value="java:jboss/datasources/YourDataSource"/>
            <module-option name="principalsQuery" value="SELECT password FROM users WHERE username=?"/>
            <module-option name="rolesQuery" value="SELECT role, 'Roles' FROM user_roles WHERE username=?"/>
            <!-- 自定义参数 -->
            <module-option name="maxFailedAttempts" value="5"/>
            <module-option name="banDuration" value="300000"/> <!-- 5分钟=300000毫秒 -->
        </login-module>
    </authentication>
</security-domain>

扩展:持久化失败记录

如果担心服务器重启丢失封禁数据,可以把失败记录存在数据库中:

  • 创建表user_failed_logins:username VARCHAR(50) PRIMARY KEY, failed_count INT, last_failed_time BIGINT, banned_until BIGINT
  • 在自定义登录模块中,用JDBC操作这个表来读写失败记录,替代内存中的ConcurrentHashMap

方案二:利用Elytron的事件监听(适用于Elytron安全域)

如果你使用Wildfly 16默认的Elytron安全框架,可以通过SecurityEventListener监听登录失败事件,在事件处理器中维护失败次数和封禁逻辑:

  • 实现SecurityEventListener接口,在authenticationFailed方法中处理失败次数累加
  • 配置Elytron的security-domain时关联这个监听器
  • 可以结合Elytron的CredentialStore或者自定义的存储来保存封禁数据

方案三:结合外部fail2ban(IP级封禁,补充方案)

如果你的需求也包含IP级的封禁,可以用传统的fail2ban工具:

  • 配置Wildfly将登录失败日志输出到文件(比如server.log),日志格式要包含用户名和IP
  • 配置fail2ban的规则,匹配日志中的登录失败条目,达到次数后封禁对应的IP
  • 注意:这个方案是IP级的,不是用户ID级的,适合补充用户级封禁的不足

总结

最贴合你需求的是自定义登录模块,既可以用内存存储快速实现,也能扩展为持久化存储保证数据不丢失。实现起来难度不大,只需要继承内置的数据库登录模块,添加失败次数统计和封禁逻辑即可。

内容的提问来源于stack exchange,提问作者j-dimension

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 21:58:12