Authelia 登录频率限制(Regulation)配置全指南:防暴力破解的账户封禁机制
【免费下载链接】autheliaThe Single Sign-On Multi-Factor portal for web apps. OpenID Certified™ and Post-Quantum Cryptography Ready.项目地址: https://gitcode.com/GitHub_Trending/au/authelia
Authelia内置的Regulation(频率限制)系统能够在用户名/密码认证端点检测到过多失败尝试时,对账户进行临时封禁,从而有效抵御暴力破解攻击。本文以 docs/content/configuration/security/regulation.md 为核心骨架,结合internal/regulation、internal/configuration、internal/handlers等模块的源码实现,系统讲解regulation配置块的每一个参数、底层判定算法、与认证流程的集成方式,以及通过authelia storage bans命令手动管理封禁条目的实战方法。
读完本文你将能够:理解modes、max_retries、find_time、ban_time四个配置项的精确语义与默认值;掌握封禁判定(计数窗口、成功重置、封禁时长)的真实算法;学会排查与运维封禁条目,让账户在ban_time到期后自动恢复登录。
一、Regulation 是什么:为什么要限流
Regulation 是 Authelia 安全体系中的一层认证防爆破(brute-force prevention)机制。当攻击者针对用户名/密码登录端点(即第一因素认证,1FA)反复尝试弱口令或撞库时,Regulation 会统计find_time时间窗口内的失败次数,一旦达到max_retries阈值,即按配置的modes封禁用户账号或来源 IP,封禁持续ban_time时长。
与前端限流、验证码等方案不同,Regulation 是服务端、持久化的:
- 每次认证尝试(成功或失败)都会写入存储层(MySQL/PostgreSQL/SQLite)的认证日志表;
- 封禁记录(
BannedUser/BannedIP)同样持久化到数据库,Authelia 重启后依然有效; - 封禁到期后记录不会自动删除,但查询时通过时间条件判定其已失效,账户即可恢复登录。
二、Configuration:完整配置示例
Regulation 配置位于configuration.yml的regulation顶层键下,一个完整可用的最小示例:
regulation: modes: - 'user' - 'ip' max_retries: 3 find_time: '2m' ban_time: '5m'字段说明速览:
| 配置项 | 类型 | 默认值 | 作用 |
|---|---|---|---|
modes | list(string) | ['user'] | 封禁的生效对象:user(封账号)或ip(封来源 IP) |
max_retries | integer | 3 | 达到该失败次数即触发封禁;设为0可完全禁用 Regulation |
find_time | string,integer(duration) | 2 minutes | 统计失败尝试的时间窗口 |
ban_time | string,integer(duration) | 5 minutes | 触发封禁后的封禁时长 |
提示:在文档站点中该示例块带有 “config-alert-example” 提示,表示这是可复制到
configuration.yml的推荐基线配置。其中同时启用了user与ip两种模式,属于比较严格的双重防护配置。
三、Options:四个配置参数深度解析
3.1 modes:封禁对象(user / ip)
modes决定封禁的主体是谁。文档明确指出推荐使用ip模式,并强调一个重要行为:无论当前配置了哪种封禁模式,只要数据库中已经存在有效的封禁记录,该用户或 IP 都会被拒绝访问——也就是说,modes只影响“自动产生新封禁”的判定路径,而不影响“已存在的封禁是否生效”的检查路径。
| 模式 | 描述 |
|---|---|
user | 封禁的对象是用户账号,即max_retries次失败均发生在同一用户名下 |
ip | 封禁的对象是远程来源 IP,即max_retries次失败均来自同一 IP |
从源码看,模式的解析发生在 regulator.go 的 NewRegulator:
users: config.MaxRetries > 0 && utils.IsStringInSlice(typeUser, config.Modes), ips: config.MaxRetries > 0 && utils.IsStringInSlice(typeIP, config.Modes),注意两个细节:
max_retries为 0 时无论modes配了什么,users和ips都为false,Regulation 整体失效,这与文档中 “Setting this option to 0 disables regulation entirely” 的描述完全一致;modes的合法取值仅有user与ip,其余值会在配置校验阶段直接报错。校验逻辑见 validator/regulation.go:
for _, mode := range config.Regulation.Modes { switch mode { case "ip", "user": break default: validator.Push(fmt.Errorf(errFmtRegulationInvalidMode, mode)) } }对应测试 regulation_test.go 验证了:单独ip、单独user、["ip","user"]均合法,["invalid"]或混合非法值会被拒绝,空modes列表则回落到默认值['user']。
3.2 max_retries:失败阈值
max_retries是触发封禁所需的失败次数。默认值3,即 3 次失败即可封禁。设置为0会完全禁用 Regulation(如上文NewRegulator中所示,0 会让users/ips同时为 false)。
阈值语义在 regulator.go 的 expires 方法 中体现得非常清晰:
// 如果 find_time 窗口内的失败次数小于 max_retries,则不封禁 if len(failures) < r.config.MaxRetries { return nil }也就是说,失败次数必须达到(>=)max_retries才会封禁。
3.3 find_time:失败统计时间窗口
find_time是“在多久的时间范围内统计失败尝试”。类型为string,integer,语法为 Go duration 格式,例如'2m'、'30s'、'1h'。默认值2 minutes。
文档中的经典示例:设置max_retries: 3、find_time: '2m',意味着用户必须在2 分钟内累计 3 次失败登录才会被禁。
底层实现中,find_time被转换为一个“起始时间点”,用于在存储层查询该时间点之后的认证日志。见 regulator.go 的 HandleAttempt:
since := r.clock.Now().Add(-r.config.FindTime) r.handleAttemptPossibleBannedIP(ctx, since) r.handleAttemptPossibleBannedUser(ctx, since, username)而存储层的 SQL 查询按time > ?过滤(sql_provider_queries.go):
SELECT time, successful FROM authentication_logs WHERE time > ? AND username = ? AND auth_type = '1FA' AND banned = ? ORDER BY time DESC LIMIT ?;注意查询条件auth_type = '1FA'——只有第一因素(密码/用户名)认证的失败才会计入 Regulation 统计,这也印证了 Regulation 防的是“用户名/密码端点”的暴力破解。
3.4 ban_time:封禁时长
ban_time是封禁持续的时间。默认值5 minutes,到期后账户自动恢复登录。它同样使用 duration 语法。
封禁到期时间的计算方式值得注意(regulator.go 的 expires 方法):
expires := failures[0].Time.Add(r.config.BanTime) return &expires即:封禁的到期时间 = 窗口内第一次失败尝试的时间 +ban_time,而不是“触发封禁的时刻 +ban_time”。因此封禁会在“首次失败后的ban_time”时点自动解除。
3.5 配置校验规则:find_time 与 ban_time 的关系
校验器强制约束find_time必须小于或等于ban_time,否则配置加载失败。错误信息定义在 validator/const.go:
regulation: option 'find_time' must be less than or equal to option 'ban_time'这一约束的合理性在于:统计窗口(find_time)不应大于封禁周期(ban_time),否则可能出现“封禁已过期但统计窗口仍然覆盖着更早的失败记录”的语义混乱。对应测试见 regulation_test.go。
同时,find_time与ban_time若被设置为非正值(如-1),会被重置为默认值(validator/regulation.go),相关行为在TestShouldSetDefaultRegulationTimeDurationsWhenNegative中有覆盖。
四、工作流程:从一次失败登录到账户被禁
将配置与源码串起来,一次完整的 Regulation 判定流程如下:
- 认证前检查:在
FirstFactorPasswordPOST处理器执行密码校验之前,先调用Regulator.BanCheck(ctx, username)检查该用户或来源 IP 是否已在封禁中(handler_firstfactor_password.go)。若命中封禁(返回regulation.ErrUserIsBanned),直接返回401 Unauthorized,不再执行密码比对; - 记录尝试:无论密码校验成功与否,都会通过
doMarkAuthenticationAttempt→Regulator.HandleAttempt记录一条认证日志(response.go),写入AuthenticationAttempt结构对应的authentication_logs表(model/regulation.go); - 触发统计:
HandleAttempt中,仅当满足“本次尝试失败 + 当前无生效封禁 + Regulation 已启用(ips || users)+ 认证类型为 1FA”四个条件时,才执行封禁判定(regulator.go); - 分别检查 IP 与用户:
handleAttemptPossibleBannedIP与handleAttemptPossibleBannedUser各自查询find_time窗口内的失败记录(按 IP 或按用户名),调用expires计算封禁到期时间; - 写入封禁:达到阈值后,向
banned_ips/banned_users表写入一条Source: "regulation"、Reason: "Exceeding Maximum Retries"的封禁记录(regulator.go、model/regulation.go)。
expires判定算法的关键点(regulator.go):
- 失败记录按时间倒序遍历,遇到第一条成功记录立即停止计数——也就是说,窗口内只要有一次成功登录,之前的失败不再累计;
- 当失败计数达到
max_retries后停止继续累加(已经该禁了); - 早于
since(即超出find_time窗口)的记录被跳过; - 最终以失败列表中的最早一条失败时间加上
ban_time作为封禁到期时间。
一个值得注意的行为:成功登录会“重置”窗口内的失败累计。这在
expires的break loop分支中体现——一旦遇到Successful == true的记录就停止统计,因此攻击者若在窗口内有一次成功登录,其此前的失败将不计入封禁判定。
五、封禁的解除与手动管理:authelia storage bans
文档明确提示:无论当前配置的封禁模式如何,数据库中已存在的封禁记录始终生效;封禁条目可通过authelia storage bans命令族手动管理。该命令的定义见 commands/storage.go,文档参考见 docs/content/reference/cli/authelia/authelia_storage_bans.md。
命令层级结构:
authelia storage bans ├── user │ ├── list │ ├── add <username> │ └── revoke [username] └── ip ├── list ├── add <ip> └── revoke [ip]5.1 查看封禁列表
authelia storage bans user list authelia storage bans ip list对应 SQL 只返回当前仍有效的封禁:revoked = FALSE AND (expires IS NULL OR expires > NOW())(见 sql_provider_queries.go)。
5.2 手动添加封禁
authelia storage bans user add john --duration "1 day" --reason "suspicious activity" authelia storage bans ip add 203.0.113.5 --permanent --reason "abuse"add子命令支持的参数(storage.go):
| 参数 | 缩写 | 默认值 | 说明 |
|---|---|---|---|
--duration | -d | 1 day | 封禁时长,与--permanent互斥 |
--permanent | -p | false | 永久封禁(expires为 NULL) |
--reason | -r | 空 | 封禁原因,写入reason字段 |
若同时指定--permanent与--duration,命令会报错拒绝执行。
5.3 撤销封禁
authelia storage bans user revoke john authelia storage bans ip revoke 203.0.113.5 # 按封禁记录 ID 撤销 authelia storage bans user revoke --id 42revoke可通过-i/--id指定封禁记录 ID,或直接传用户名/IP 值。撤销后该条封禁的revoked标记为真,不再影响登录。
5.4 手动封禁 vs 自动封禁的差异
手动添加的封禁与 Regulation 自动产生的封禁共用同一张banned_users/banned_ips表,但Source字段不同:自动封禁的Source恒为"regulation"、Reason为"Exceeding Maximum Retries",而手动封禁可以自定义Reason。由于文档明确“只要数据库中存在有效封禁记录即拒绝访问”,手动封禁同样会立即生效,可用于应急封禁某账号或某恶意 IP。
六、最佳实践与注意事项
- 推荐使用
ip模式:user模式容易被攻击者利用来“锁死”他人账号(拒绝服务),而ip模式对正常用户影响更小。文档明确推荐ip; - 同时启用两种模式需谨慎:
user+ip双模式意味着同一来源 IP 和账号都可能被禁,防护更严但误伤面更大; max_retries: 0会彻底关闭 Regulation:如果希望仅保留“已存在封禁仍然生效”的能力而不再自动产生新封禁,可以这样配置,但注意这会失去自动防爆破能力;find_time与ban_time的关系:find_time必须<=ban_time,且封禁到期时间基于“窗口内最早一次失败 +ban_time”,调优时需结合真实登录频率;- 封禁记录会一直保留在数据库中:到期只是“失效”而非“删除”,可通过
authelia storage bans user list/authelia storage bans ip list定期审计,必要时手动撤销; - Regulation 只统计 1FA 认证:从 SQL 查询条件
auth_type = '1FA'可以看出,TOTP、WebAuthn 等第二因素的失败不计入封禁统计。
七、相关源码与文档导航
- 配置文档:docs/content/configuration/security/regulation.md
- 配置 Schema 与默认值:internal/configuration/schema/regulation.go
- 配置校验:internal/configuration/validator/regulation.go、internal/configuration/validator/regulation_test.go
- Regulation 核心实现:internal/regulation/regulator.go、internal/regulation/types.go、internal/regulation/const.go
- 认证流程集成:internal/handlers/handler_firstfactor_password.go、internal/handlers/response.go
- 存储层结构与 SQL:internal/model/regulation.go、internal/storage/sql_provider_queries.go
- 封禁管理命令:internal/commands/storage.go、docs/content/reference/cli/authelia/authelia_storage_bans.md
【免费下载链接】autheliaThe Single Sign-On Multi-Factor portal for web apps. OpenID Certified™ and Post-Quantum Cryptography Ready.项目地址: https://gitcode.com/GitHub_Trending/au/authelia
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考