WeKan 管理员面板「锁定用户(Locked Users)」实战指南:暴力破解防护配置与账户解锁 WeKan 管理员面板「锁定用户Locked Users」实战指南暴力破解防护配置与账户解锁【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekan导读本文围绕 WeKan 管理后台的Admin Panel → People → Locked Users锁定用户功能展开讲解暴力破解登录防护的完整机制多少个连续失败登录尝试会锁定账户、锁定多长时间、当前有哪些用户正被锁定以及如何查看、过滤与解锁。读完本文你将掌握 known/unknown 两套差异化防护参数的配置方法理解账户锁定的底层判定逻辑按用户 来源地址的计数范围、正确密码立即放行、递增式登录延迟等并能熟练使用「解锁单个用户」与「解锁全部用户」两项管理操作。功能定位Locked Users 解决什么问题在 Locked-Users.md 中该功能被明确定位为暴力破解brute-force防护当登录失败次数达到阈值后账户会被临时锁定管理员可以在该页面查看「当前正被锁定的人是谁」并调整「多少次失败触发锁定」「锁定持续多久」等参数。配套的 brute-force-protection.md 将其概括为四项核心能力可配置设置管理员直接在管理后台调整锁定参数无需改代码已知用户与未知用户使用不同规则注册用户与不存在的登录尝试分别计数可视化标识被锁定用户在界面中以红色锁图标标识解锁能力管理员可以单独解锁某个用户也可以一次性解锁全部用户。Unlock all users解锁全部用户同时也是 People人员管理 页面操作行中的一项动作与该页面共享同一套锁定状态数据。已知用户与未知用户为什么必须分开计数原始文档强调了一个容易被忽略的设计要点系统对「已知用户」和「未知用户」分别维护一套独立的计数与锁定阈值。已知用户Known Users指已存在的账户。按「用户 来源地址」维度计数保护真实账户不被远程锁死。未知用户Unknown Users指登录时使用了不存在账户名/邮箱的地址。按「来源 IP/客户端地址」维度计数防止攻击者通过批量猜测用户名把真实用户的账户「提前锁死」。从源码看这一分离贯穿整个实现packages/wekan-accounts-lockout/src/knownUser.js与packages/wekan-accounts-lockout/src/unknownUser.js分别是两套独立的状态机各自挂接Accounts.validateLoginAttempt钩子、各自管理自己的计数与解锁调度。源码注释明确说明在正常使用中用户名是公开的看板与卡片成员都会被列出因此攻击者挑选目标是很容易的把已知用户与未知用户分开计数可以避免「猜用户名反而把真人锁得更快」这种逆向效果。配置项详解六 两个核心参数在Admin Panel → People → Locked Users页面可以看到两套参数每套三个此外源码中还提供两个可选的登录延迟参数。各参数含义与默认值如下已知用户Known Users / 注册用户参数含义默认值Failures Before Lockout触发锁定前的连续失败次数3Lockout Period账户被锁定的时长秒60Failure Window失败计数的时间窗口秒窗口内失败累计窗口过期后重新开始计数15未知用户Unknown Users / 不存在的用户名参数含义默认值Failures Before Lockout触发 IP 封禁前的连续失败次数3Lockout PeriodIP 被封禁的时长秒60Failure Window失败计数的时间窗口秒15递增式登录延迟源码级扩展参数除了上述三参数models/lockoutSettings.js的getKnownConfig()还暴露出两个仅存在于已知用户配置中的进阶参数不会出现在标准管理界面表单中loginDelayBase默认 1 秒第一次密码错误后需要等待的时长设为0可完全关闭延迟使锁定行为回到旧版「阶梯式」表现loginDelayMax默认 30 秒延迟倍增的上限。其底层逻辑见 lockoutDecision.js第 n 次失败后的等待时长按base × 2^(n-1)指数增长并用max封顶。这样的设计让攻击者的成本远高于真人真人只是偶尔输错且防护是「变慢」而非「一刀切锁死」——账户始终可以重试只是越来越慢。参数存储与启动加载从数据库到钩子所有参数不是硬编码在代码里的而是持久化在 MongoDB 的lockoutSettings集合中文档结构由 lockoutSettings.js 定义每条记录包含value数值、category、sort、createdAt、modifiedAt_id采用known-failuresBeforeLockout、known-lockoutPeriod、known-failureWindow、unknown-failuresBeforeLockout等命名。getKnownConfig()与getUnknownConfig()两个 helper 会一次性批量读取相关配置而不是发起三次查询读取失败时回退到 3/60/15 默认值。服务端启动流程见 accounts-lockout-config.jsMeteor.startup后延迟约 2 秒等待数据库就绪然后从lockoutSettings集合读出两套配置实例化AccountsLockout并调用startup()startup()内部会分别启动KnownUser与UnknownUser两个实例见 accountsLockout.js二者再通过Accounts.validateLoginAttempt与Accounts.onLogin钩子接入 Meteor 的登录流程。当管理员在界面上修改参数并点击保存后见 lockedUsersBody.js 的js-lockout-save事件前端会通过LockoutSettings.update写入数据库并调用Meteor.call(reloadAccountsLockout)让运行中的实例重新加载配置。查看与管理锁定用户锁定用户列表Locked Users标签页会展示所有当前处于锁定状态的用户每条记录包含用户名Username邮箱地址Email address失败次数Number of failed attempts剩余锁定时间Remaining lock time列表数据由服务端方法getLockedUsers见 lockedUsers.js提供只有管理员可以调用查询条件是在services.accounts-lockout.lockedUntil大于当前时间即锁定尚未到期的用户返回结果通过 accountLockout.js 的lockSummary()汇总出failedAttempts、lockedAddresses当前被锁定的来源地址数量、unlockTime与remainingLockTime秒。前端 lockedUsersBody.js 会将超过 60 秒的剩余时间格式化为Xm Ys的可读形式。解锁单个用户在列表中点击某个被锁定用户旁边的红色锁图标即可单独解锁。前端会弹出确认框对应 i18n 文案accounts-lockout-confirm-unlock确认后调用Meteor.call(unlockUser, userId)见 lockedUsers.js。该服务端方法同样校验管理员权限并确保目标用户存在随后通过$unset清除该用户services.accounts-lockout下的全部锁定状态。解锁全部用户点击Unlock All按钮可以一次性解除当前所有被锁定用户。对应方法unlockAllUsers见 lockedUsers.js对所有存在lockedUntil字段的用户文档执行$unsetmulti: true同样需要管理员权限。由于该操作影响面大前端会先弹出确认框accounts-lockout-confirm-unlock-all。另外People 页面 的操作行中也有Unlock all users按钮二者共享同一后端方法。刷新列表页面提供「刷新」按钮js-refresh-locked-users与#refreshLockedUsers两个事件均绑定到refreshLockedUsers()用于在解锁操作后重新拉取最新锁定列表。People 面板的锁定状态过滤在Admin Panel → People的人员列表中可以通过控件行中的筛选下拉框按锁定状态过滤用户选项包括 all全部、locked已锁定、active活跃、inactive未活跃、admin管理员。选择Locked Users Only即可只显示当前因失败登录尝试被锁定的用户。列表的每一行中活跃状态与锁定状态都是可点击的红色锁图标标识锁定管理员可以直观地定位并处理问题账户搜索框与过滤条件结合方便在用户量较大时快速定位。注意该页面的总数统计的是整个结果集而非当前分页。底层实现原理源码级的防护细节这一功能远不止「计数 锁门」那么简单其底层实现包含若干经过安全审计加固的关键决策值得深入理解1. 计数器按「用户 来源地址」划分作用域旧版实现为每个用户只维护一个全局计数器任何人只要知道用户名就能用三次错误密码把这个账户从所有地址锁死且可反复进行源码注释将其记录为 GHSA-rf3w-rj48-jxcc 的两个缺陷之一。修复后计数作用域变为「用户 来源地址」见 lockoutScope.js攻击者只能锁死自己攻击所用的地址账户主人仍然可以从自己的地址正常登录。每个地址的计数存放在services.accounts-lockout.byAddress.sha256哈希下地址经 SHA-256 哈希后取前 32 个十六进制字符作为字段名——既规避了 Mongo 字段名不能包含点号IPv4 地址全是点的限制也避免在用户文档里留存攻击来源地址的明文记录。反向代理场景下真实客户端地址从X-Forwarded-For右侧数HTTP_FORWARDED_COUNT个见 lockoutScope.js该规则与 REST 登录节流 loginAttemptThrottle.js 保持一致。2. 锁定期间正确密码「永远放行」并清除锁定GHSA-rf3w-rj48-jxcc 的另一个缺陷是旧代码在锁定期间连正确密码也拒绝并把它继续计为一次失败——真人在锁定期输入正确密码反而会延长自己的锁定。修复后decideKnownUserAttempt将「密码正确」的判断放在最前面见 lockoutDecision.js任何无需猜测就能证明身份的人都不是锁定机制要防的对象因此正确密码总是被允许并立即清除该作用域的锁定状态clearLockout。同理Accounts.onLogin钩子也会在成功登录后清除残留计数避免状态残留影响下一次登录。3. 锁定期间继续尝试不会延长锁定期处于锁定中仍继续输错系统返回locked动作并告知剩余秒数但不会重置或延长unlockTime见 lockoutDecision.js。否则攻击者只要在锁定期内持续敲击就能无限期地把真实所有者的解锁时间往后推——这等于在防护机制内部又开了一个拒绝服务DoS的口子。4. 失败计数基于结构字段而非错误文案Meteor 默认开启ambiguousErrorMessages所有凭据错误都会被统一改写为同一句模糊提示因此旧版依赖英文错误原因字符串Incorrect password/User not found来决定是否计数的逻辑永远无法触发导致锁定形同虚设源码注释记录为 GHSA-2g94-9x3m-hv37。现在的实现改为根据登录尝试的结构字段判断见 loginFailureDecision.js是否密码登录、是否有用户匹配、尝试是否失败。同时no-2fa-code2FA 第一步请求验证码发生在密码已验证之后被显式排除在失败计数之外避免 2FA 用户正常登录几次就被锁而invalid-2fa-code2FA 验证码错误有意纳入计数因为连续输错验证码同样是真实的猜测行为。5. 锁定只影响「一个地址」lockedUntil只是展示字段services.accounts-lockout.lockedUntil是一枚展示专用字段它是该账户所有被锁地址中解锁时间最晚的那个纯粹为了让管理界面以及 Mongo 无法对动态命名字段做聚合查询的现实能够回答「哪些账户当前处于锁定」这一问题。锁定判定本身永远不会读取它——真正决定锁定状态的是各地址的unlockTime。该约定在 accountLockout.js 中被明确注释并配套isUserLocked、lockSummary等 helper 供 People 表格的锁图标、解锁点击处理器和 lockedUsers 方法三方共用确保三处读取逻辑一致。当某个地址的锁定期自然到期或管理员手动解锁后如果不再有任何地址处于锁定lockedUntil也会被同步清除见 knownUser.js。6. 锁定事件写入安全日志每次锁定触发时服务端会通过注入的onLockout回调把事件写入安全日志见 accounts-lockout-config.js记录brute.lockout事件、blocked动作、来源DDP login、用户 ID、用户名、IP、地理位置与详细描述如「在 N 次错误密码后锁定了账户的某个地址持续 X 秒」。这些记录可在Admin Panel → Problems中查看。值得注意的工程细节是日志上报被try/catch包裹且从不await——防护是主任务记录不能拖慢或破坏锁定流程本身。安全建议来自 brute-force-protection.md 的官方建议以默认值3 次失败 / 60 秒锁定期 / 15 秒窗口作为起点再根据自身安全需求调整高安全环境可考虑延长锁定期Lockout Period定期查看锁定用户列表识别潜在攻击模式例如短时间内大量不同的用户名被锁定可能意味着针对性的撞库尝试结合递增式登录延迟loginDelayBase/loginDelayMax让攻击者的每次尝试成本指数级上升同时避免真实用户被一步锁死。相关文档与源码索引本文主题文档Locked-Users.md暴力破解防护总览brute-force-protection.md人员管理页面People.md配置存储模型lockoutSettings.js服务端启动与日志上报accounts-lockout-config.js锁定/解锁服务端方法lockedUsers.js锁定状态汇总模型accountLockout.js前端页面逻辑lockedUsersBody.js锁定决策纯函数lockoutDecision.js计数作用域与地址解析lockoutScope.js失败判定结构字段loginFailureDecision.js已知用户实现knownUser.js未知用户实现unknownUser.js【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekan创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考