Files
orbit/docs/console-login-security.md

5.9 KiB
Raw Permalink Blame History

控制台登录保护

从 main 的数据库迁移 000035 开始启用。仅保护管理端账号密码登录,不改变业务 SDK Token、Worker 或结果订阅认证。保持单管理员,不引入角色管理系统。

默认行为

  • 15 分钟失败窗口内连续失败 5 次,临时锁定 15 分钟。成功登录清空失败计数;失败窗口或锁定自然到期后重新计数。锁定期间尝试正确密码也不能登录,继续请求不会延长锁定。
  • 同一账号每分钟最多受理 10 次,同一来源 IP 每分钟最多 30 次。固定窗口从首次尝试开始;成功登录不清空限流计数。任意一项触发就拒绝。
  • 账号不存在、停用、密码错误或绑定凭据失效,正常拒绝均为 401。未知账号也参与相同限流/锁定,使用相同成本的 bcrypt 校验。既有账号按规范用户 ID 计数,避免等价 Unicode 登录名绕过限制。
  • 触发锁定或限流返回 429,响应包含 Retry-After 秒数。登录页显示原因和倒计时;刷新页面不能绕过服务端限制。已登录会话不受一次新登录锁定影响,不中断业务服务。
  • 数据库或审计提交失败返回 503,不签发 Cookie。跨站登录请求为 403;非法/过大的 JSON 为 400。

所有保护状态存放 MySQL,多节点共用,重启不归零。昂贵的密码校验在事务外执行:先用短事务占用请求预算,校验结束后再次锁定账号保护行;失败计数、锁定、成功会话和最终审计在同一事务提交。并发结果不能越过已经生效的账号锁定。校验进程异常退出的审计保留 PENDING,超过 10 分钟由清理器标 ERROR;不将未知结果当作密码错误。

部署配置

所有 Orbit 节点必须使用一致配置;不允许用 0 关闭保护。修改部署配置并重启服务后生效,不重置已有计数或已记录的锁定截止。降低阈值后按新阈值检查。

环境变量 默认值 约束 / 含义
ORBIT_CONSOLE_LOGIN_FAILURE_LIMIT 5 1–100 次,触发临时锁定
ORBIT_CONSOLE_LOGIN_FAILURE_WINDOW 15m 1s–24h,连续失败窗口
ORBIT_CONSOLE_LOGIN_LOCK_DURATION 15m 1s–24h,新触发锁定时长
ORBIT_CONSOLE_LOGIN_ACCOUNT_LIMIT 10 1–1000 次 / 账号 / 限流窗口
ORBIT_CONSOLE_LOGIN_IP_LIMIT 30 1–10000 次 / IP / 限流窗口
ORBIT_CONSOLE_LOGIN_RATE_WINDOW 1m 1s–24h,固定限流窗口
ORBIT_CONSOLE_LOGIN_AUDIT_DAYS 90 1–3650 天,登录审计独立保留期
ORBIT_CONSOLE_TRUSTED_PROXIES 空 明确信任的代理 CIDR,逗号分隔

普通 Compose 和 HA Compose 均透传这些变量。本地测试环境在 .local/compose-test/compose.env 修改所需配置,再执行 python3 scripts/orbit-local.py up。此文件含其他私密配置,不提交仓库;不能仅在启动脚本外 export,脚本会隔离外部项目环境变量。

默认只使用 TCP 对端 IP,忽略客户端自带的 X-Forwarded-For、X-Real-IP。部署域名反向代理时,将实际代理的地址或精确网段填入 ORBIT_CONSOLE_TRUSTED_PROXIES,例如同机代理 127.0.0.1/32,::1/128。只在直连对端可信时解析 X-Forwarded-For,从右向左剥离可信代理;遇到第一个非可信地址停止。格式非法或跳数过多时退回 TCP 对端。代理应覆盖或按可信链追加转发头,不能把所有互联网地址都配置为受信代理。未配置时所有经代理的请求共用代理 IP 配额,账号维度仍独立生效。

查看审计

管理端右上角“登录审计”,仅部署指定的 ORBIT_CONSOLE_APPLICATION_ADMIN 可查看,默认每页 10 条。显示时间、登录名、来源 IP、结果和可重试时间;记录用户名不代表该账号一定存在。SDK Bearer、未登录或普通控制台账号无权读取全局记录。

接口:GET /console/auth/audit?before=<上页next>,认证为有效 HttpOnly 管理员会话 Cookie。响应:

{"data":{"items":[{"id":"42","username":"operator","remote_ip":"192.0.2.1","user_agent":"browser","outcome":"LOCKED","created_at":"2026-09-29T12:00:00Z","finished_at":"2026-09-29T12:00:00Z","retry_at":"2026-09-29T12:15:00Z"}],"next":"42"}}

next 仅在还有下一页时提供,是倒序分页的排他上界;无全库计数、不承诺跨页快照。结果类型:SUCCESS、INVALID、LOCKED、ACCOUNT_LIMIT、IP_LIMIT、PENDING、ERROR。审计从不保存密码、Cookie、Token 或这些秘密的摘要,浏览器列表不会自动拉取接入 Token。

登录拒绝示例:

HTTP/1.1 429 Too Many Requests
Retry-After: 900
Cache-Control: no-store
Content-Type: application/json

{"error":{"code":"LOGIN_LOCKED","message":"连续登录失败,已临时锁定,请稍后重试","retry_after_seconds":900}}

限流代码为 LOGIN_RATE_LIMITED。不要让客户端自动循环重试账号密码登录。

保留与升级

迁移只新增 orbit_console_login_guards 与 orbit_console_login_audit,无外键,不更改已有用户、密码、会话、凭据及业务记录。先备份,停止旧节点,再前向迁移和统一部署;旧版没有保护,不能和新版混跑。

每分钟清理一次,每种语句最多处理 500 行:48 小时未更新的保护状态、超过配置天数的登录审计,以及超时 PENDING 的状态归类。有效保护窗口最长 24 小时,48 小时非活动清理不会提前解除有效锁定。清理有索引、有截止时间,失败保留记录并写错误日志;这不会缩小数据库已分配磁盘,也不替代备份。业务“保留策略”管理的是命名空间历史,登录审计的全局保留时间由上述独立配置管理。

锁定自然到期后使用正确密码登录即可;不自动改密码、撤销业务 Token 或踢出已有会话。若持续遭受攻击,管理员应按审计中的来源限制访问并核对反向代理配置,不能通过关闭计数绕过保护。