Skip to content

安全体系

Zenith Admin 内置了多层安全防护能力,涵盖 IP 访问控制、账号锁定、密码策略、验证码及注册开关,均可通过系统配置页面在运行时动态调整。


IP 访问控制

通过 ipAccessMiddleware 对所有 /api/* 请求进行 IP 过滤,支持白名单黑名单两种模式(可同时启用,黑名单优先执行)。

配置项

在后台「系统设置 → IP 访问控制」页面配置(对应 system_configs 表中的以下 key):

配置 Key类型说明
ip_whitelist_enabledboolean是否启用白名单。启用后只有名单内的 IP 可访问
ip_whiteliststring (JSON 数组)白名单 IP 列表,如 ["192.168.1.0/24", "10.0.0.1"]
ip_blacklist_enabledboolean是否启用黑名单。启用后名单内 IP 访问将收到 403
ip_blackliststring (JSON 数组)黑名单 IP 列表,支持单 IP 与 CIDR 网段

工作机制

请求进入 /api/*

  ├── 免检路径(直接放行):
  │   /api/auth/login、/api/auth/captcha、/api/auth/register
  │   /api/auth/refresh、/api/auth/forgot-password、/api/auth/reset-password
  │   /api/oauth/*、/api/auth/oauth/*

  ├── 两者均未启用 → 直接放行

  ├── 黑名单已启用 → 命中则 403

  └── 白名单已启用 → 未命中则 403
  • IP 来源优先从 X-Forwarded-For 请求头中读取(取第一个值),其次读取 X-Real-IP,再从 TCP 连接信息读取,兜底为 127.0.0.1
  • 支持 CIDR 网段匹配(如 192.168.1.0/24),基于 ip-range-check 库实现
  • 配置缓存 30 秒,修改后台配置后最多延迟 30 秒生效
  • 命中黑名单或未命中白名单时写入 ip_access_logs,可通过 GET /api/ip-access-logs 分页查询拦截记录

Nginx 反代注意:确保 Nginx 已正确设置 X-Real-IPX-Forwarded-For,否则后端收到的将是内网 IP。


账号锁定

连续登录失败达到阈值后,账号会被自动锁定一段时间,有效防止暴力破解。

相关配置项

配置 Key类型默认值说明
login_max_attemptsnumber10最大失败次数,达到后自动锁定
login_lock_duration_minutesnumber30锁定持续时长(分钟)

工作机制

  • 失败计数以 {REDIS_KEY_PREFIX}login_attempt:{username} 为 key 存储于 Redis,锁定标记为 {REDIS_KEY_PREFIX}login_lock:{username},服务重启后不重置
  • 锁定到期后自动解除
  • 管理员可在「用户管理」列表中点击「解除锁定」按钮,提前解除指定账号的锁定状态(调用 POST /api/users/:id/unlock

密码策略

系统支持通过系统配置控制密码复杂度要求与过期策略。

复杂度配置项

配置 Key类型默认值说明
password_min_lengthnumber6密码最小长度
password_require_uppercasebooleanfalse是否必须包含大写字母
password_require_special_charbooleanfalse是否必须包含特殊字符(!@#$%^&* 等)

密码复杂度由 packages/server/src/lib/password-policy.ts 读取并校验,后端在用户管理的创建用户、管理员修改指定用户密码、批量重置密码、导入用户等场景触发校验;前端通过 GET /api/system-configs/password-policy 读取策略并展示输入提示。

密码过期

配置 Key类型默认值说明
password_expiry_enabledbooleanfalse是否开启密码过期强制重置。开启后,当密码超期未更新,登录时将强制跳转修改密码页
password_expiry_daysnumber90密码有效期天数,仅在 password_expiry_enabledtrue 时生效

过期流程

  1. 用户登录时,若 password_expiry_enabled = true,后端计算 passwordUpdatedAt + password_expiry_days 是否早于当前时间
  2. 若已过期,登录响应中的 requirePasswordChangetrue
  3. 前端检测到 requirePasswordChange 后,弹出「强制修改密码」弹窗
  4. 用户完成密码修改后,password_updated_at 更新,过期状态解除

登录验证码

配置 Key类型默认值
captcha_enabledbooleanfalse
captcha_complexitystringmedium

启用后,登录页自动显示图形验证码输入框。验证码通过 GET /api/auth/captcha 获取(返回 SVG + captchaId),登录时需同时提交 captchaId 和用户输入的验证码文本,后端校验后自动失效。

  • 验证码为数学表达式 SVG,5 分钟有效
  • 验证码存储在服务端内存 Map 中,校验后一次性删除
  • captcha_complexity 控制验证码复杂度(干扰强度与识别难度):low(干扰线少、运算简单)/ medium(默认)/ high(干扰线多、运算范围大),非法值按 medium 处理

注册开关

配置 Key类型默认值
allow_registrationbooleanfalse
  • false:登录页不显示「注册」入口,POST /api/auth/register 返回 403
  • true:开放注册,登录页显示「注册账号」链接

生产建议:如非公开注册场景,建议保持 allow_registration = false


安全相关接口速查

接口说明
GET /api/auth/captcha获取验证码(返回 SVG + captchaId)
POST /api/auth/register开放注册(受 allow_registration 控制)
POST /api/users/{id}/unlock管理员解除账号锁定
PUT /api/auth/password当前用户修改密码
POST /api/auth/forgot-password发送找回密码邮件
POST /api/auth/reset-password使用重置 token 设置新密码
PUT /api/users/{id}/password管理员修改指定用户密码

CSRF 防护

基于 hono/csrf 中间件校验请求的 Origin 头,防止第三方网站伪造表单或 AJAX 请求。

配置

通过环境变量 ALLOWED_ORIGINS 配置允许的来源白名单(在 .env 中设置):

dotenv
# 留空 = 开发模式,不限制来源
ALLOWED_ORIGINS=

# 生产环境示例(逗号分隔)
ALLOWED_ORIGINS=https://admin.example.com,https://app.example.com

放行规则

  • 请求无 Origin 头(服务端调用、Postman、curl)→ ✅ 直接放行
  • ALLOWED_ORIGINS 为空(开发模式)→ ✅ 直接放行
  • Origin 在白名单中 → ✅ 放行
  • Origin 不在白名单中 → ❌ 403 Forbidden

生产建议:务必配置 ALLOWED_ORIGINS,否则任何来源的请求均可通过 CSRF 检查。


请求体大小限制

通过环境变量 REQUEST_BODY_LIMIT 配置全局请求体大小上限,单位为字节:

dotenv
# 0 = 不启用 bodyLimit 中间件,使用运行时默认行为
REQUEST_BODY_LIMIT=0

当值大于 0 时,所有请求都会经过 hono/body-limit 校验;超出限制返回:

json
{
  "code": 413,
  "message": "请求体超出大小限制",
  "data": null
}

请求超时

通过环境变量 REQUEST_TIMEOUT_MS 配置 /api/* 请求超时时间,单位为毫秒:

dotenv
# 0 = 不启用 timeout 中间件
REQUEST_TIMEOUT_MS=0

超时返回:

json
{
  "code": 408,
  "message": "请求处理超时(30000ms)",
  "data": null
}

以下长耗时路径不应用超时中间件:/api/ws/api/files/api/db-backups/api/db-admin/api/log-files/api/monitor/stream/api/ai/conversations


接口限流

基于 hono-rate-limiter + Redis 对高危接口进行限流,防止暴力破解和滥用。

当前限流策略

内置三组规则,启动时从 rate_limit_rules 表加载到内存;数据库为空时内存使用代码默认值,后台规则列表接口会将默认规则落库:

规则名默认绑定接口窗口限制默认启用keyType提示文案
authPOST /api/auth/login3 分钟20 次falseip登录尝试过于频繁,请 3 分钟后再试
captchaGET /api/auth/captcha60 秒30 次trueip验证码请求过于频繁,请稍后再试
sensitivePOST /api/auth/registerPOST /api/auth/forgot-passwordPOST /api/auth/reset-password60 分钟5 次trueip操作过于频繁,请 1 小时后重试

超过限制时返回,message 以具体规则的 blockedMessage 为准:

json
{
  "code": 429,
  "message": "操作过于频繁,请稍后再试",
  "data": null
}

实现细节

  • keyType 支持 ipuserip_path:分别按客户端 IP、登录用户(未登录回退 IP)、IP + path 计数
  • 计数器存储在 Redis(key 前缀:{REDIS_KEY_PREFIX}rl:),服务重启后持续计数
  • 命中与拦截统计存储在 Redis(key 前缀:{REDIS_KEY_PREFIX}rlstats:),保留命中数、拦截数、最近拦截记录与 24 小时小时序列
  • 后台「系统设置 → 接口限流」通过 GET /api/rate-limit/rulesPATCH /api/rate-limit/rules/{id}POST /api/rate-limit/rules 动态管理规则,保存后立即热更新
  • 自定义规则可通过 pathPatterns 绑定路径,支持精确路径与 /* 前缀匹配;全局 pathBoundRateLimit 会自动应用匹配规则
  • 支持 POST /api/rate-limit/unblock 解封指定 key,以及 POST /api/rate-limit/reset-stats 清空指定规则统计

反代注意:确保 Nginx 正确透传 X-Forwarded-ForX-Real-IP,否则计数可能落在反向代理或连接层 IP 上,导致正常请求被误限。

Built with VitePress for local documentation preview.