Nginx UI 认证安全配置指南:IP 白名单与登录失败封禁(IPWhiteList / BanThresholdMinutes / MaxAttempts)
2026/9/24 22:55:44 网站建设 项目流程
  • 后端
  • 前端
  • 运维
  • MCP 服务

【免费下载链接】nginx-ui

Yet another WebUI for Nginx

项目地址:https://gitcode.com/gh_mirrors/ngi/nginx-ui
点击查看免费下载

本文以 Nginx UI 配置文件的[auth]段为核心,系统讲解从 v2.0.0-beta.26 起引入的授权选项:IP 白名单(IPWhiteList)与登录失败封禁(BanThresholdMinutesMaxAttempts)。结合仓库源码,你将理解这三项配置的生效链路、边界行为(本机回环豁免、代理场景、IPv6)、失败计数与解封机制,并掌握通过配置文件与NGINX_UI_AUTH_*环境变量两种方式完成安全加固的完整方案。

一、[auth]配置段概览

从 v2.0.0-beta.26 版本开始,Nginx UI 支持在配置文件的auth段设置授权选项。该段对应的 Go 结构体定义在 settings/auth.go,源码结构如下:

type Auth struct { IPWhiteList []string `json:"ip_white_list" binding:"omitempty,dive,ip|redacted" ini:",,allowshadow" protected:"true"` TrustedProxies []string `json:"trusted_proxies" binding:"omitempty,dive,ip|cidr|redacted" ini:",,allowshadow" protected:"true"` BanThresholdMinutes int `json:"ban_threshold_minutes" binding:"min=1"` MaxAttempts int `json:"max_attempts" binding:"min=1"` SecureSessionTimeoutMinutes int `json:"secure_session_timeout_minutes" binding:"min=1"` }

从源码可以看出,[auth]段不仅包含文档中列出的三项,还包含TrustedProxies(受信代理列表)与SecureSessionTimeoutMinutes(安全会话超时时间,默认值 10 分钟,定义于同一文件的DefaultSecureSessionTimeoutMinutes常量)两个相邻安全选项。它们共同构成 Nginx UI 的认证入口防护体系。其中:

  • IPWhiteList为字符串数组,支持重复键声明(ini:",,allowshadow"表示同名键可多次出现,每次追加一个元素),这正是配置示例中多次书写IPWhiteList的语法依据;
  • BanThresholdMinutesMaxAttempts均要求最小值 1(binding:"min=1"),不允许配置为 0 或负数。

该配置段在 settings/settings.go 中注册为auth节(sections.Set("auth", AuthSettings)),并可通过环境变量覆盖(见后文第五节)。

二、IPWhiteList:访问来源 IP 白名单

2.1 配置语法

  • 类型:string(可重复声明,形成白名单列表)
  • 示例:10.0.0.1
  • 支持 IPv4 与 IPv6(如2001:0000:130F:0000:0000:09C0:876A:130B
[auth] IPWhiteList = 10.0.0.1 IPWhiteList = 10.0.0.2 IPWhiteList = 2001:0000:130F:0000:0000:09C0:876A:130B

2.2 行为规则与回环豁免

默认情况下,如果没有设置IPWhiteList,所有 IP 地址都允许访问 Nginx UI。一旦设置了白名单:

  • 只有白名单内的 IP 与127.0.0.1可以访问 Nginx UI;
  • 其余来源将收到403 Forbidden错误;
  • 白名单为空时视为未启用,放行所有 IP。

该逻辑实现在 internal/middleware/ip_whitelist.go,核心代码如下:

func IPWhiteList() gin.HandlerFunc { return func(c *gin.Context) { clientIP := c.ClientIP() if len(settings.AuthSettings.IPWhiteList) == 0 || clientIP == "127.0.0.1" || clientIP == "::1" { c.Next() return } if !lo.Contains(settings.AuthSettings.IPWhiteList, clientIP) { c.AbortWithStatus(http.StatusForbidden) return } c.Next() } }

值得注意的细节:

  • 回环豁免同时覆盖 IPv4 与 IPv6:代码同时判断了127.0.0.1::1。换言之,本机访问(无论走 IPv4 还是 IPv6 回环地址)始终被放行,即使它们未出现在白名单中。文档只提到127.0.0.1,源码证明::1同样享有豁免——这在启用 IPv6 的本机环境中非常重要。
  • 白名单精确匹配:使用lo.Contains进行列表精确匹配,不会做 CIDR 网段匹配。如果需要放行整个网段,必须在[auth]段逐一列出该网段内的每个 IP(或结合反向代理统一出口 IP 的策略)。若需要 CIDR 能力,可关注TrustedProxies字段(其 binding 校验为ip|cidr),它用于声明可信的反向代理来源,从而让c.ClientIP()正确解析经代理转发后的真实客户端 IP。

2.3 白名单中间件的挂载位置

IPWhiteList中间件在 router/routers.go 中被挂载到 API 路由组根节点上:

root := r.Group("/api", middleware.IPWhiteList())

这意味着白名单约束作用于整个/api前缀下的全部接口——无论是登录接口还是业务接口,均受其管辖。这是访问控制的第一道闸门,在认证(登录鉴权)之前生效。

2.4 测试用例对行为边界的印证

internal/middleware/ip_whitelist_test.go 中的测试精确刻画了白名单的边界行为,值得运维人员关注:

  • 代理场景:当请求来自受信代理(trustedProxies=["127.0.0.1"])且X-Forwarded-For头携带198.51.100.20时,白名单只放行198.51.100.20,而放行203.0.113.20则返回 403 —— 说明白名单判定的是经ClientIP()解析后的"真实客户端 IP",而非 TCP 对端地址;
  • 伪造头防护:当不存在受信代理时,客户端直接伪造X-Forwarded-For: 203.0.113.20也无法绕过白名单(返回 403),即伪造的转发头不会被信任;
  • 异常输入失败关闭:当远程地址无法解析为合法 IP 时,白名单判定为拒绝(403),遵循"失败关闭"(fail-closed)的安全原则;
  • 回环兼容127.0.0.1[::1]在未配置受信代理的情况下直接放行(204 No Content)。

因此,如果你在反向代理(Nginx、Caddy 等)后面部署 Nginx UI,请务必同时配置TrustedProxies指向代理服务器地址,否则ClientIP()取到的是代理地址,白名单可能无法按预期匹配真实客户端 IP。

三、登录失败封禁机制:BanThresholdMinutes 与 MaxAttempts

3.1 参数说明

参数类型默认值语义
BanThresholdMinutesint10失败计数与封禁的有效时间窗口(分钟)
MaxAttemptsint10触发封禁所需的累计失败次数阈值

默认行为:如果用户在10 分钟内登录失败10 次,该用户来源 IP 将被禁止登录10 分钟

源码层面的默认值定义于 settings/auth.go 的初始化逻辑:

var AuthSettings = &Auth{ BanThresholdMinutes: 10, MaxAttempts: 10, SecureSessionTimeoutMinutes: DefaultSecureSessionTimeoutMinutes, }

同时在 settings/settings.go 的Init中做了一次兜底校验:若配置值小于等于 0,则回退为默认值 10:

if AuthSettings.BanThresholdMinutes <= 0 { AuthSettings.BanThresholdMinutes = 10 } if AuthSettings.MaxAttempts <= 0 { AuthSettings.MaxAttempts = 10 }

注意:这里只对<= 0的情况兜底,而结构体 binding 校验要求min=1,因此合法取值区间为1及以上的正整数。若需更严苛的安全策略,可将两者调低(如BanThresholdMinutes = 5MaxAttempts = 3),即"5 分钟内失败 3 次即封禁 5 分钟"。

3.2 计数与封禁的实现链路

封禁记录使用数据库表ban_ips(对应模型 model/ban_ip.go),通过 query/ban_ips.gen.go 生成的 GORM 查询对象操作。核心逻辑在 internal/user/login.go:

func BanIP(ip string) { b := query.BanIP banIP, err := b.Where(b.IP.Eq(ip)).First() if err != nil || banIP.ExpiredAt <= time.Now().Unix() { _ = b.Create(&model.BanIP{ IP: ip, Attempts: 1, ExpiredAt: time.Now().Unix() + int64(settings.AuthSettings.BanThresholdMinutes*60), }) return } _, _ = b.Where(b.IP.Eq(ip)).UpdateSimple(b.Attempts.Add(1)) }

其工作机制可归纳为:

  1. 按 IP 查询封禁记录;
  2. 若不存在该记录,或记录已过期(ExpiredAt <= now),则新建一条记录:Attempts = 1,过期时间 = 当前时间 +BanThresholdMinutes分钟(换算为秒);
  3. 若记录仍有效,则将Attempts累加 1

在 api/user/auth.go 的Login处理器中,登录失败(密码错误、2FA 缺失或校验失败等)都会调用user.BanIP(clientIP)。而在登录入口处,会先做封禁检查:

banIP, _ := b.Where(b.IP.Eq(clientIP), b.ExpiredAt.Gte(time.Now().Unix()), b.Attempts.Gte(settings.AuthSettings.MaxAttempts), ).Count() if banIP > 0 { c.JSON(http.StatusTooManyRequests, LoginResponse{ Message: "Max attempts", Code: ErrMaxAttempts, // 4291 }) return }

即:当某 IP 存在"未过期"且"失败次数 ≥ MaxAttempts"的记录时,登录请求直接返回429 Too Many Requests(业务错误码4291,常量ErrMaxAttempts)。登录成功后,该 IP 的封禁记录会被清除:

// login success, clear banned record _, _ = b.Where(b.IP.Eq(clientIP)).Delete()

3.3 封禁的粒度与观察维度

  • 按 IP 独立计数:封禁记录以 IP 为键,不同来源 IP 的失败计数互不影响。这一行为由 internal/user/login_ban_test.go 中的TestBanIPKeepsDifferentClientBucketsSeparate测试直接验证:对198.51.100.10203.0.113.20分别调用BanIP后,两条记录的Attempts各自独立(分别为 2 和 1),互不干扰。
  • 窗口滑动特性:一旦超过BanThresholdMinutes,旧记录即视为过期,下一次失败会重建记录并从 1 重新计数。因此不存在"永久封禁"——封禁是周期性的。
  • 额外的人为延迟:登录失败后,服务端会追加一个 0~10 秒的随机休眠(time.Sleep(random * time.Second),见 api/user/auth.go),进一步抬高暴力破解的时间成本。

3.4 被封禁 IP 的管理接口

api/settings/auth.go 提供了两个管理接口:

  • GET查询当前封禁的 IP 列表(GetBanLoginIP):先清理已过期的封禁记录,再返回满足ExpiredAt >= now && Attempts >= MaxAttempts的记录;
  • 删除指定被封禁的 IP(RemoveBannedIP):管理员可手动解除误封。

即当某个合法用户因多次输错密码被临时封禁时,管理员无需等待BanThresholdMinutes到期,可以通过管理接口立即解除封禁。

四、完整配置示例与生效方式

4.1 配置文件示例

将上述配置组合到 Nginx UI 的配置文件(默认为app.ini,具体路径与加载方式见 config-app.md):

[auth] # 仅允许以下 IP 访问(可重复声明),未配置时放行所有 IP IPWhiteList = 10.0.0.1 IPWhiteList = 10.0.0.2 IPWhiteList = 2001:0000:130F:0000:0000:09C0:876A:130B # 5 分钟内累计失败 5 次,则封禁该 IP 5 分钟 BanThresholdMinutes = 5 MaxAttempts = 5 # 可选:反向代理场景下声明受信代理,保证 ClientIP() 取到真实客户端地址 TrustedProxies = 127.0.0.1

配置保存后,服务重启或通过管理端保存设置时,settings.Save()会将结构体回写到 ini 文件并重新加载(settings.Reload()),实现热更新。

4.2 环境变量方式(Docker / systemd 部署)

由于 settings/settings.go 中envPrefixMapAUTH映射到AuthSettings,且环境变量前缀为NGINX_UI_,因此 docs/zh_CN/guide/env.md 列出的对应关系如下:

配置项环境变量
IPWhiteListNGINX_UI_AUTH_IP_WHITE_LIST
BanThresholdMinutesNGINX_UI_AUTH_BAN_THRESHOLD_MINUTES
MaxAttemptsNGINX_UI_AUTH_MAX_ATTEMPTS

Docker 部署示例:

docker run -d \ -e NGINX_UI_AUTH_IP_WHITE_LIST=10.0.0.1 \ -e NGINX_UI_AUTH_BAN_THRESHOLD_MINUTES=5 \ -e NGINX_UI_AUTH_MAX_ATTEMPTS=5 \ -p 9000:9000 \ uozi/nginx-ui:latest

提示:IPWhiteList为数组类型,若需多个 IP,请参考 docs/zh_CN/guide/env.md 中数组类配置的写法(逗号分隔或重复声明,视版本而定)。

五、安全建议与注意事项

  1. 白名单与反向代理IPWhiteList判定的是c.ClientIP()的解析结果。在 Nginx/Caddy 反向代理之后部署时,务必配置TrustedProxies指向代理地址,否则所有请求都会显示为代理 IP,白名单要么全部放行、要么全部拒绝。这一点已由ip_whitelist_test.go中的代理与伪造头用例明确验证。
  2. 回环豁免是设计行为127.0.0.1::1始终可访问,即使未列入白名单。这意味着"限制来源"无法阻止本机进程访问,请确保运行 Nginx UI 的主机本身是可信的。
  3. 封禁是 IP 粒度的临时策略:它是暴力破解的减速带,而非访问控制手段。对于高安全要求的部署,应结合强密码、2FA(见 api/user/auth.go 中的 OTP/Passkey 登录链路)与防火墙规则共同使用。
  4. 调参权衡MaxAttempts过小、BanThresholdMinutes过大,可能造成合法用户因输入错误被长时间拒之门外;反之则防护力度不足。建议按实际部署环境调整,并借助封禁列表管理接口及时处理误封。
  5. 配置边界BanThresholdMinutesMaxAttempts必须为不小于 1 的整数,<= 0的值会在初始化时被强制回退为 10。

六、小结

[auth]段的三项核心配置为 Nginx UI 提供了两道基础防线:IPWhiteList在 API 路由最外层按来源 IP 进行访问控制(支持 IPv4/IPv6、回环豁免、失败关闭);BanThresholdMinutesMaxAttempts则在登录入口按 IP 累计失败次数并实施周期性封禁(429 Too Many Requests),配合封禁管理接口可随时解除误封。结合TrustedProxies、环境变量注入与源码级行为边界(代理解析、IPv6 回环、独立计数桶),你可以在 Docker、systemd 与反向代理等各类部署形态下完成一次完整、可验证的登录安全加固。

如需进一步了解配置文件整体结构与环境变量全量清单,可参阅 config-app.md 与 env.md。

  • 后端
  • 前端
  • 运维
  • MCP 服务

【免费下载链接】nginx-ui

Yet another WebUI for Nginx

项目地址:https://gitcode.com/gh_mirrors/ngi/nginx-ui
点击查看免费下载

相关推荐

上一篇:Lumafly基础教程:一键启用、禁用与更新MOD的8个实用技巧
下一篇:ROCm 6.4.1 正式发布:Radeon 9070 系列获得官方支持,升级前注意这几点

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询