安全体系
Zenith Admin 内置了多层安全防护能力,涵盖 IP 访问控制、账号锁定、密码策略、验证码及注册开关,均可通过系统配置页面在运行时动态调整。
IP 访问控制
通过 ipAccessMiddleware 对所有 /api/* 请求进行 IP 过滤,支持白名单与黑名单两种模式(可同时启用,黑名单优先执行)。
配置项
在后台「系统设置 → IP 访问控制」页面配置(对应 system_configs 表中的以下 key):
| 配置 Key | 类型 | 说明 |
|---|---|---|
ip_whitelist_enabled | boolean | 是否启用白名单。启用后只有名单内的 IP 可访问 |
ip_whitelist | string (JSON 数组) | 白名单 IP 列表,如 ["192.168.1.0/24", "10.0.0.1"] |
ip_blacklist_enabled | boolean | 是否启用黑名单。启用后名单内 IP 访问将收到 403 |
ip_blacklist | string (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-IP或X-Forwarded-For,否则后端收到的将是内网 IP。
账号锁定
连续登录失败达到阈值后,账号会被自动锁定一段时间,有效防止暴力破解。
相关配置项
| 配置 Key | 类型 | 默认值 | 说明 |
|---|---|---|---|
login_max_attempts | number | 10 | 最大失败次数,达到后自动锁定 |
login_lock_duration_minutes | number | 30 | 锁定持续时长(分钟) |
工作机制
- 失败计数以
{REDIS_KEY_PREFIX}login_attempt:{username}为 key 存储于 Redis,锁定标记为{REDIS_KEY_PREFIX}login_lock:{username},服务重启后不重置 - 锁定到期后自动解除
- 管理员可在「用户管理」列表中点击「解除锁定」按钮,提前解除指定账号的锁定状态(调用
POST /api/users/:id/unlock)
密码策略
系统支持通过系统配置控制密码复杂度要求与过期策略。
复杂度配置项
| 配置 Key | 类型 | 默认值 | 说明 |
|---|---|---|---|
password_min_length | number | 6 | 密码最小长度 |
password_require_uppercase | boolean | false | 是否必须包含大写字母 |
password_require_special_char | boolean | false | 是否必须包含特殊字符(!@#$%^&* 等) |
密码复杂度由 packages/server/src/lib/password-policy.ts 读取并校验,后端在用户管理的创建用户、管理员修改指定用户密码、批量重置密码、导入用户等场景触发校验;前端通过 GET /api/system-configs/password-policy 读取策略并展示输入提示。
密码过期
| 配置 Key | 类型 | 默认值 | 说明 |
|---|---|---|---|
password_expiry_enabled | boolean | false | 是否开启密码过期强制重置。开启后,当密码超期未更新,登录时将强制跳转修改密码页 |
password_expiry_days | number | 90 | 密码有效期天数,仅在 password_expiry_enabled 为 true 时生效 |
过期流程:
- 用户登录时,若
password_expiry_enabled = true,后端计算passwordUpdatedAt + password_expiry_days是否早于当前时间 - 若已过期,登录响应中的
requirePasswordChange为true - 前端检测到
requirePasswordChange后,弹出「强制修改密码」弹窗 - 用户完成密码修改后,
password_updated_at更新,过期状态解除
登录验证码
| 配置 Key | 类型 | 默认值 |
|---|---|---|
captcha_enabled | boolean | false |
captcha_complexity | string | medium |
启用后,登录页自动显示图形验证码输入框。验证码通过 GET /api/auth/captcha 获取(返回 SVG + captchaId),登录时需同时提交 captchaId 和用户输入的验证码文本,后端校验后自动失效。
- 验证码为数学表达式 SVG,5 分钟有效
- 验证码存储在服务端内存 Map 中,校验后一次性删除
captcha_complexity控制验证码复杂度(干扰强度与识别难度):low(干扰线少、运算简单)/medium(默认)/high(干扰线多、运算范围大),非法值按medium处理
注册开关
| 配置 Key | 类型 | 默认值 |
|---|---|---|
allow_registration | boolean | false |
false:登录页不显示「注册」入口,POST /api/auth/register返回 403true:开放注册,登录页显示「注册账号」链接
生产建议:如非公开注册场景,建议保持
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 中设置):
# 留空 = 开发模式,不限制来源
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 配置全局请求体大小上限,单位为字节:
# 0 = 不启用 bodyLimit 中间件,使用运行时默认行为
REQUEST_BODY_LIMIT=0当值大于 0 时,所有请求都会经过 hono/body-limit 校验;超出限制返回:
{
"code": 413,
"message": "请求体超出大小限制",
"data": null
}请求超时
通过环境变量 REQUEST_TIMEOUT_MS 配置 /api/* 请求超时时间,单位为毫秒:
# 0 = 不启用 timeout 中间件
REQUEST_TIMEOUT_MS=0超时返回:
{
"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 | 提示文案 |
|---|---|---|---|---|---|---|
auth | POST /api/auth/login | 3 分钟 | 20 次 | false | ip | 登录尝试过于频繁,请 3 分钟后再试 |
captcha | GET /api/auth/captcha | 60 秒 | 30 次 | true | ip | 验证码请求过于频繁,请稍后再试 |
sensitive | POST /api/auth/register、POST /api/auth/forgot-password、POST /api/auth/reset-password | 60 分钟 | 5 次 | true | ip | 操作过于频繁,请 1 小时后重试 |
超过限制时返回,message 以具体规则的 blockedMessage 为准:
{
"code": 429,
"message": "操作过于频繁,请稍后再试",
"data": null
}实现细节
keyType支持ip、user、ip_path:分别按客户端 IP、登录用户(未登录回退 IP)、IP + path计数- 计数器存储在 Redis(key 前缀:
{REDIS_KEY_PREFIX}rl:),服务重启后持续计数 - 命中与拦截统计存储在 Redis(key 前缀:
{REDIS_KEY_PREFIX}rlstats:),保留命中数、拦截数、最近拦截记录与 24 小时小时序列 - 后台「系统设置 → 接口限流」通过
GET /api/rate-limit/rules、PATCH /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-For或X-Real-IP,否则计数可能落在反向代理或连接层 IP 上,导致正常请求被误限。