Skip to content

账号安全与系统设置 ​

Castor 的登录状态保存在服务端,可以查看和强制下线;在此基础上提供两步验证、邮件找回密码、密码规则和接口限流。其中会增加使用门槛的功能默认关闭,由管理员在「系统管理 → 系统配置 → 系统设置」里按需打开。

系统设置 ​

「系统设置」页保存可以在运行时修改的功能开关和参数,存在 system_settings 表中,保存后几秒内生效,无需重启。页面分「安全」「邮件」「文件存储」「AI」四个页签;邮件、文件存储和 AI 的各项见 配置项 → 系统设置里的配置,这里是「安全」页签:

设置项作用默认值
security.totp_enabled两步验证总开关关闭
security.totp_required_roles必须开启两步验证的角色无
security.password_reset_enabled邮件找回密码关闭
security.password_min_length密码最短长度(6–64)6
security.password_require_letters_digits密码必须同时包含字母和数字关闭
security.password_require_symbol密码必须包含符号关闭
security.session_ttl_hours登录有效期(小时,1–720,滑动续期)SESSION_TTL_HOURS(8)
security.login_max_failures / security.login_lockout_minutes登录失败锁定次数(3–1000)/ 锁定时长兼失败计数的时间窗(分钟,1–1440),见 登录失败锁定10 / 15
security.rate_limit_per_minute每个 IP 每分钟的 /api 和 /ws 请求上限(60–100000)600
security.auth_rate_limit_per_minute每个 IP 每分钟的登录类请求上限(3–1000;登录、两步验证码、验证身份、找回密码共用)20
security.api_tokens_enabled允许使用 API Token,见 开放接口关闭
  • 会给用户增加步骤的功能(两步验证、找回密码、API Token)在打开开关前都不生效;登录失败锁定和接口限流则从一开始就按上表的默认值生效
  • 前置条件不满足的开关不能打开,页面上会写明原因。例如「邮件」页签没有填 SMTP 服务器和网站地址时,「邮件找回密码」不能打开;演示模式下两步验证、找回密码和 API Token 都不能打开
  • SMTP 密码、S3 Secret Key、AI API Key 这类密钥用由 SECRET_KEY 派生的密钥加密后存储,接口和页面都不会再返回明文
  • 设置了对应环境变量的项(如 SMTP_HOST、AI_API_KEY)以环境变量为准,页面上只读。SESSION_TTL_HOURS 是例外:它只决定登录有效期的默认值,不会锁定该项。只有服务启动前就要用的配置(数据库地址、SECRET_KEY、端口等)必须放环境变量
  • 查看需要菜单权限 system_settings,修改需要按钮权限 system_settings_edit

系统设置的安全措施 ​

系统设置能改邮件服务器、文件存储、AI 接口这些「把数据送到哪里」的配置。一个被盗用的管理员账号如果改了它们,可能截获找回密码邮件、把新上传的文件或 AI 请求送到别人的服务器。因此:

  • 需要近期验证身份:保存设置、点测试按钮时,要求 10 分钟内登录过或验证过身份,否则会弹出「验证身份」,输入当前密码(开启了两步验证的还要输入验证码或恢复码)。只偷到登录状态(cookie)的人改不了设置;验证失败计入登录失败锁定。创建 API Token、新增或修改 Webhook 也要经过同样的验证(见 开放接口)。接口是 POST /api/admin/reauth,后端用 common/session.ts 的 requireRecentAuth(request) 保护接口
  • 变更通知:每次保存都会给所有启用的超级管理员发站内通知,写明谁改了哪些项(密钥只写「已更新 / 已清除」),操作日志里也有记录(密钥脱敏)
  • 不能指向保留地址和内网:SMTP 服务器、S3 接口地址、AI 接口地址不能指向云服务器元数据(169.254.169.254)等保留地址;生产环境默认也不能指向内网(127.0.0.1、10.x、192.168.x 等)。要用内网的 MinIO、邮件服务器时设置 SETTINGS_ALLOW_PRIVATE_NETWORK=true。保存、测试时都会检查,AI 请求在连接时还会再检查一次实际地址,防止域名事后改指向内网。用环境变量锁定的值由运维自行负责,不做这项检查
  • 只给少数人权限:查看需要菜单权限 system_settings,修改需要按钮权限 system_settings_edit,默认只有超级管理员有

生产环境建议:

  1. 在「必须开启的角色」里加上超级管理员,让所有超级管理员都用两步验证
  2. 把关键配置用环境变量锁定,例如 APP_BASE_URL(找回密码链接的网站地址)和 SMTP_HOST,这样即使管理员账号被盗也改不了
  3. 日常操作不用超级管理员账号,只把 system_settings_edit 给真正需要的人

新增一个设置项 ​

设置项定义在 apps/api/src/common/settings.ts 的 SETTING_DEFINITIONS 里,每项写明分组、类型(布尔、整数、字符串、密钥、枚举、字符串列表)、默认值、取值范围、可以锁定它的环境变量、是否公开(公开的会通过 /api/admin/app-info 发给未登录的页面)以及不可用的原因。新的环境变量名还要加进 common/settings-env.ts。前端在 apps/web/src/modules/admin/pages/settings/form.ts 的 FIELD_META 里补上标签和说明,在同目录 index.tsx 对应页签里放上这个字段,并在该页面的 locales/ 里补译文。代码里通过 app.settings.get() 读取;在每个请求都会用到的地方(例如限流)用 app.settings.peek(),它直接返回缓存,不等待数据库。

新功能如果需要「默认关闭、管理员可以打开」,应该在这里加一个设置项,而不是再加一个环境变量。

服务端会话 ​

  • 登录后在 sessions 表中新建一条会话。cookie castor_session(加密、HttpOnly、SameSite=Lax)只保存会话 ID 和 CSRF token,登录状态与用户以表中记录为准
  • 带会话的 /api/ 写请求(POST / PUT / PATCH / DELETE)必须在 X-CSRF-Token 请求头里带上这个 CSRF token;前端的请求封装会自动处理,见 后端开发
  • 每个请求校验一次会话;最近活动时间和到期时间每分钟最多更新一次(滑动续期)
  • 过期或已撤销超过一天的会话由调度器进程每小时清理一次

以下操作会让会话立即失效,对方下一次请求返回 401:

操作影响范围
退出登录当前会话
修改密码本人的其他会话(当前设备保持登录)
管理员修改密码、停用或删除用户该用户的全部会话
通过邮件链接重置密码该用户的全部会话
强制下线指定的会话

后端判断是否已登录时统一用 common/session.ts 的 isSignedIn(request),不要读取 cookie 中的字段。

在线用户 ​

「系统管理 → 安全审计 → 在线用户」列出当前已登录的会话,包括用户、设备、IP、登录时间和最近活动时间,结果按操作人的 数据权限 过滤。拥有按钮权限 system_sessions_revoke 时可以强制下线:不能下线自己当前的会话,非超级管理员不能下线超级管理员。

每个用户在「个人设置 → 登录设备」里可以看到自己在哪些设备上登录,并退出单个设备或其他所有设备。

两步验证 ​

在系统设置中打开两步验证后:

  1. 用户在「个人设置 → 两步验证」中用验证器 App(Google Authenticator、Microsoft Authenticator、1Password 等)扫码,输入第一个验证码完成绑定,同时得到 10 个恢复码(只显示这一次)
  2. 已绑定的用户登录时,密码正确后还要输入 6 位验证码,或者用一个恢复码代替
  3. 在「必须开启的角色」里选中的角色,成员登录时如果还没绑定,会先要求绑定,绑定完成后直接进入系统;这些用户不能自己关闭两步验证
  4. 用户丢失手机和恢复码时,管理员在「用户管理 → 编辑用户」弹窗左下角重置其两步验证(需要 system_users_edit,非超级管理员不能重置超级管理员)

实现细节:

  • 使用 TOTP(6 位、30 秒、SHA-1),允许前后各一个时间窗的时钟误差;同一时间窗的验证码只能用一次
  • 密钥用从 SECRET_KEY 派生的密钥做 AES-256-GCM 加密后存储;更换 SECRET_KEY 后已有的绑定会失效,需要重新绑定
  • 恢复码只存 sha256 哈希,每个只能用一次
  • 验证码输错与密码输错一样计入 登录失败锁定
  • 密码正确但还没通过第二步时,会话处于等待状态,不能访问任何需要登录的接口,5 分钟后失效;通过后会换发新的会话 ID。「登录成功」日志和最近登录时间在第二步通过后才记录
  • 关闭总开关只是让登录不再询问验证码,已有的绑定会保留,重新打开后立即恢复

找回密码 ​

先在系统设置的「邮件」页签填好 SMTP 服务器和网站地址(可以用「发送测试邮件」试一下,见 配置项),再在「安全」页签打开。打开后登录页会出现「忘记密码?」:

  1. 用户输入账号绑定的邮箱。无论邮箱是否存在,页面提示都一样,不会泄露账号是否存在
  2. 邮件中的链接形如 <网站地址>/reset-password?token=…,30 分钟内有效、只能使用一次;再次申请会让之前的链接失效
  3. 设置新密码时按密码规则校验;重置后该用户所有设备退出登录。已开启两步验证的账号下次登录仍需要验证码

邮件语言跟随用户当前的界面语言。本地开发时可以设置 MAIL_DRIVER=log,邮件不会发出,而是打印在后端日志里。

密码规则 ​

设置、修改或重置密码时都按系统设置中的规则校验,包括修改密码、新增和编辑用户、导入用户以及找回密码。已有的密码不受影响。前端表单从 app-info 读取同一套规则,提交前就能提示。

接口限流 ​

  • 所有 /api 和 /ws 请求按 IP 计数,超过每分钟上限返回 429,报错按语言翻译,响应头带 Retry-After;静态文件和 /health 不计数
  • 登录、两步验证码(登录时和绑定时)、验证身份、找回密码(申请邮件和设置新密码)共用一个更严格的额度,即 security.auth_rate_limit_per_minute
  • 计数保存在进程内存中,多实例部署时每个实例分别计数,实际上限最多是设置值乘以实例数
  • RATE_LIMIT_ENABLED=false 可以整体关闭(测试环境默认关闭)

限流与登录失败锁定同时生效:限流限制请求频率,锁定限制失败次数。

登录失败锁定 ​

  • 计为失败的有:密码错误(包括已停用账号输对了密码)、登录时两步验证码或恢复码错误、验证身份失败(密码或验证码错误)。统计最近 security.login_lockout_minutes 分钟内的次数,按 IP 和按用户名分别计数
  • 任一计数达到 security.login_max_failures 后,登录、第二步验证和验证身份都返回 429,直到较早的失败移出时间窗。演示模式下只按 IP 计数,避免有人故意输错把公共演示账号锁住
  • 登录成功后,该用户名和该 IP 之前的失败不再计数;这些记录仍保留在登录日志中
  • LOGIN_MAX_FAILURES / LOGIN_LOCKOUT_MINUTES 可以锁定这两个值,见 配置项

Released under the MIT License.