# 多租户与鉴权安全契约 更新时间:2026-06-28 这个系统后续要卖给同行作为题库 SaaS,因此租户隔离、鉴权、资源权限和审计是商用红线。前端可以先按迁移期接口联调,但正式上云验收前必须完成本文件的 P0 项。 ## 安全边界 | 边界 | 规则 | | --- | --- | | 平台超级管理员 | 可管理全部租户、SaaS 套餐、订阅、账单、用量、公共题库 | | 租户管理员 | 只能管理自己租户的品牌、域名、成员、内容、营销、订单和 CRM | | 租户成员 | 按角色和 permissions 访问,例如运营、教师、销售、代理 | | 学生用户 | 只能访问自己所在租户下被授权的内容和自己的学习数据 | | 公共题库 | 必须通过平台授权/租户采纳后才对租户可见 | | 私有资源 | 必须通过后端权限校验后签名访问,不能前端直连对象存储 | ## 当前迁移期状态 当前后端仍存在这些迁移期实现: - `x-tenant-id` 用于租户上下文。 - `x-user-id` 或 body/query 的 `userId` 用于用户上下文。 - `x-platform-admin-key` 用于平台管理员接口。 - 本地短信 provider 可使用 `mock`。 - 默认开发密钥存在于 `.env.example` 和 config fallback。 这些只允许用于本地开发和内网联调,不允许作为正式云端验收方案。 ## P0:正式云端测试前必须完成 1. 正式用户鉴权 - 使用 Supabase Auth/JWT 或服务端 session 解析可信 userId。 - 禁止前端通过 query/body/header 指定 userId。 - `GET /api/auth/me` 返回当前用户、租户成员、角色、权限。 2. 正式租户上下文 - H5 可由域名解析租户。 - 小程序可由 tenantCode 解析租户。 - 后端必须校验用户是否属于该租户。 - 跨租户请求必须返回 403 或 404。 3. 平台管理员鉴权 - 替换 `x-platform-admin-key`。 - 平台管理员也要有 JWT/session、角色、审计日志。 4. 生产配置 fail-fast - `NODE_ENV=production` 时禁止默认 `AUTH_CODE_PEPPER`。 - 禁止默认 `AUTH_SESSION_SECRET`。 - 禁止默认 `PLATFORM_ADMIN_API_KEY`。 - 禁止 `CORS_ORIGIN=*`。 - 禁止 `AUTH_SMS_PROVIDER=mock`。 5. 请求体大小限制 - 普通 JSON API 必须有默认上限。 - 导入接口可以有更大上限,但必须可配置且有最大值。 - 超限返回 413。 6. 租户密钥保护 - 商户密钥、短信 secret、OAuth secret 不允许明文长期存储。 - 生产应使用 KMS/Vault 或 envelope encryption。 - API 只返回 `secretRef`、掩码和配置状态。 7. RLS 与 API 双层回归 - 数据库 RLS 要按 `tenant_id` 拦截。 - API SQL 必须显式带 `tenant_id`。 - 测试必须覆盖跨租户读取、写入、下载、后台权限越权。 ## 前端必须遵守 - 不信任本地缓存里的 tenantId/userId 作为安全依据。 - 不在页面里保存或展示任何商户密钥、短信密钥、OAuth secret。 - 不在前端硬编码对象存储 bucket、私有资源路径、商户号。 - 不在前端自行开通会员权益;必须通过订单/支付/激活码后端流程。 - 不允许“切换销售归属”这类破坏首绑保护的入口,除非后端提供带权限的管理接口。 - 不在前端直接判断“这个用户能不能看某题/某视频/某资料”的最终结果;必须请求后端。 - 切换租户、退出登录、登录新账号时,清理旧租户缓存和用户缓存。 ## 后端接口约定 所有业务表查询必须满足: ```text where tenant_id = currentTenantId ``` 管理类写接口必须满足: ```text requireTenantPermission(auth, ':') ``` 平台类接口必须满足: ```text requirePlatformAdmin(auth) ``` 资源下载必须满足: ```text content_assets 台账存在 资源属于当前 tenant 资源状态允许访问 用户权益满足 visibility/access_rules 返回短期签名 URL ``` 支付 webhook 必须满足: ```text 验签通过 provider event id 幂等 订单 tenant_id 匹配 金额以后端订单金额为准 事务内更新 payment/order/entitlement 记录 payment_events ``` ## 权限点现状 当前默认角色: | 角色 | 默认权限 | | --- | --- | | tenant_owner | `*` | | tenant_admin | `*` | | tenant_operator | 内容、营销、兑换码/优惠券只读、客资/CRM 只读 | | teacher | 内容维护 | | sales | 兑换码、优惠券、客资 | | agent | 兑换码/优惠券只读、本人的客资 | | student | 无后台权限 | 后续要补: - 租户自定义角色模板。 - 菜单级、模块级、字段级权限。 - 权限变更审计。 - 班级/教师/学生范围权限。 ## 上线前安全验收清单 - `npm audit` 为 0 高危/严重漏洞。 - `npm run check:refactor` 通过。 - 生产环境启动时默认密钥 fail-fast 生效。 - 跨租户学生读取题目/订单/资料返回拒绝。 - 销售只能查看自己权限范围内客资。 - 代理不能查看其他代理客资。 - 教师不能修改租户商户密钥。 - 学生不能访问租户后台接口。 - 未开通权益不能下载 SVIP 资料或播放会员视频。 - 支付 webhook 重放不会重复开通权益。 - 激活码并发兑换只能成功一次。 - 对象存储签名 URL 有短 TTL。 - 后台关键操作写入 audit log。