Files
gongxue-base/docs/refactor/multitenant-auth-security-contract.md
2026-06-28 20:37:53 +08:00

157 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 多租户与鉴权安全契约
更新时间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, '<scope>:<action>')
```
平台类接口必须满足:
```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。