docs: rewrite documentation from current implementation

This commit is contained in:
2026-07-30 13:30:24 +08:00
parent 5100854795
commit d895e1da63
23 changed files with 553 additions and 1612 deletions

View File

@@ -1,98 +0,0 @@
# 认证与授权待补强清单
> 2026-07-29 实施状态可信代理启动校验、外部登录成员生命周期、Redis 跨实例频控与故障关闭、固定目录数据库 Capability、事务化 System Scope 审计、MassTransit EF Bus/Consumer Outbox 与即时 BackgroundJob Consumer、浏览器 Cookie/CSRF 主链路及 endpoint manifest 已落地。本地 RabbitMQ 4.3.4 已验证停机期间事务提交、outbox 积压及重启补发;生产网关 ACL 和生产 Broker 演练仍属于部署验收项。
当前生效规则见 [认证、授权与 Host 安全策略](authentication-authorization-security.md)。本文只记录尚需补强的安全事项,不重复描述已实现体系。
## P0可信代理与 Host fail-closed
- Production 必须配置正式 `PlatformHosts``TrustedProxyAddresses`
- Production 不允许只保留 `localhost` / `127.0.0.1` 作为平台 Host。
- 未受信来源伪造 `X-Forwarded-Host` 不能改变 realm 或 tenant context。
- 受信代理只接受一跳转发,网关必须覆盖客户端伪造的 Forwarded Headers。
- API 公网入口必须只能由受信网关访问。
实现说明:应用已强制 `ForwardLimit=1`Production 缺少正式 Host、显式 `AllowedHosts` 或可信代理地址时启动失败;公网 ACL 和网关覆盖转发头由部署层落实。
验收:
- Host A + Tenant B token 返回 403。
- 未知 Host 的非豁免路径返回 404。
- Production 缺少可信代理或正式平台 Host 时启动失败。
## P0Disabled / Invited 成员生命周期
- 外部身份登录不得静默恢复 Disabled membership。
- Invited membership 不得被微信登录静默激活。
- 首次外部登录是否允许创建学生成员,必须由租户自注册策略控制。
- 成员恢复只能由管理员显式操作并写审计。
实现说明:`TenantAuthPolicy.AllowExternalStudentSelfRegistration` 控制首次外部登录Disabled/Invited 不会被登录激活,管理员成员变更会同步撤销 Session、写审计并发布生命周期事件。
验收:
- Disabled 成员旧 access/refresh 立即失效。
- Disabled 成员不能通过微信 Web 或小程序登录恢复。
- 关闭自注册时,首次外部登录被拒绝。
## P1SaaS Capability 授权
RBAC 只回答“用户是否有操作权限”Capability 负责“租户是否购买、启用并可使用该能力”。
默认组合:
```text
Tenant Active
+ Subscription 有效
+ Module / Feature 可用
+ Operation Permission
+ DataScope / Resource Scope
```
实现说明:`SaasFeature`、显式 `PermissionModule.RequiredFeatureCode`、不可变套餐版本、`TenantFeatureOverride``IFeatureAccessService` 已进入数据库授权链路。未知 Feature、未购买模块和失效订阅均 fail-closed角色绑定保留历史权限但鉴权和菜单只使用当前有效权限。
验收:
- 有 permission 但套餐不含模块,返回 403。
- 套餐包含模块但没有 permission返回 403。
- PastDue / Cancelled / Expired 不能创建新的受限资源。
- 修改套餐后,旧 access token 不需要等待过期即可失去能力。
## P1接口最小权限与 DataScope 审计
- 建立 endpoint authorization manifestmethod、route、realm、module、permission、DataScope、audit action。
- 后台写接口不得只使用 `[Authorize]`
- tenant/platform 权限不得串用。
- `[AllowAnonymous]` 只能出现在白名单路由。
- All-only 资源必须显式声明。
- 新增 Controller action 未进入 manifest 时测试失败。
实现说明:`AuthorizationManifestTests` 对全部 Controller HTTP Action 的 method、route、匿名标记和 policy 生成稳定摘要MVC convention 同时为全部非匿名 Controller endpoint 生成 realm、module、permission、operation、All-only 与 audit action 运行时元数据,变更会触发测试失败并要求安全评审。
验收:
- 列表、详情、创建、更新、删除、批量、导出和 Worker job 使用一致 DataScope。
- 租户 A 管理员不能读取或操作租户 B 数据。
## P1System Scope 审计
- `ITenantExecutionScope` 创建 System Scope 时必须记录 caller、reason、target tenant 和 request/job id。
- 平台操作、Worker、迁移验证和受审计公共题库服务才允许使用 System Scope。
- 跨租户写操作必须落 `AuditLog`
实现说明:`SystemScopeRequest` 强制 caller、reason、target tenant 和 correlation ID没有租户目标时必须显式声明 `IsGlobal`,且 Worker/公共题库不能创建全局 scope。旧参数签名已移除。成功路径的 entered 审计、跨租户业务写入和 completed 审计处于同一 PostgreSQL 事务,异常路径回滚业务并持久化 entered/failed 审计。
验收:
- 未声明 reason 的 System Scope 创建失败。
- Worker scope 不串租户。
- 高风险平台操作都有审计记录。
## P2客户端与协议规范
- 浏览器 token 存储策略在正式前固定:纯 Bearer、本域 BFF 或 cookie 方案只能选一种主链路。
- Access token 继续短期有效,不把角色和权限写入 JWT。
- 登录审计和错误响应避免泄露手机号、openId、邮箱完整值。
- 出现第三方生态登录、开放 API 或多客户端授权需求时,再评估 OpenIddict / OIDC不继续扩展私有协议。
实现说明:浏览器使用 `/api/browser-auth` + Secure/HttpOnly Cookie + Origin/CSRF 校验;原 `/api/auth` Bearer 契约继续供小程序、原生和服务调用。

View File

@@ -1,146 +0,0 @@
# 认证、授权与 Host 安全策略
本文是当前生效安全规范。新增接口或修改登录流程时以本文档和自动化测试为准前端菜单、JWT 字符串和历史角色约定不能代替 API 授权。
## 授权域
- `tenant`:租户业务域,必须绑定 Active 租户和 Active `TenantMembership`
- `platform`:平台运营域,只能从配置的 Platform Host 进入,不绑定租户。
核心规则:
- Host、JWT scope、tenant claim、数据库 Session 和请求租户上下文必须一致。
- JWT 只证明已认证会话,不承载可直接授权的角色或权限。
- 后台权限每次从数据库角色绑定解析;菜单只控制 UI 展示。
- 租户后台能力同时要求 Active tenant、有效订阅、模块权益和 operation permissionCapability 仍以 PostgreSQL 为准。
- 数据权限必须进入 SQL无法可靠映射 owner、region 或 class 的资源采用 All-only fail-closed。
- 用户、成员、租户、后台角色、权限、SecurityStamp 或 Session 任一失效,旧 token 不能继续取得能力。
- Controller 默认要求认证;公开接口必须显式 `[AllowAnonymous]`
```text
Client
-> Trusted proxy
-> TenantResolutionMiddleware
-> JWT + AuthSession validation
-> Authorization handler + current access context
-> EF tenant filter + DataScope SQL + PostgreSQL constraints
```
## 账号与 Session
- 账号由 ASP.NET Core Identity 管理。
- 密码最少 8 位且必须同时包含字母和数字PBKDF2 迭代次数 210,000。
- 连续 5 次密码失败后锁定 15 分钟。
- 普通租户用户使用手机号和密码或手机号短信验证码登录;平台管理员当前使用账号和密码登录。
- 微信等外部身份只保存 provider subject、openid、unionid不保存 `session_key` 或原始 secret。
- Data Protection key 持久化到 PostgreSQL非 Development 环境必须提供带私钥的 PKCS#12 证书保护 key ring。
Access token
- RSA SHA-256 签名Header 必须包含 `kid`
- 固定 15 分钟。
- 包含 `sub``sid``jti``iat``iss``aud``exp``scope`
- tenant token 必须包含 `tid`platform token 禁止包含 `tid`
- 不包含 role 或 permission claim。
Refresh token
```text
v2.{t|p}.{tenantId|-}.{sessionId}.{64-byte-random-secret}
```
- 数据库只保存完整 refresh token 的 SHA-256 hash。
- 刷新在事务内轮换 Session。
- 并发刷新只允许一个成功。
- 已轮换 token 被复用时视为重放,撤销整个 token family 并写审计。
- logout 撤销当前 refresh token familylogout-all 更新 SecurityStamp 并撤销用户全部 Session。
浏览器入口使用 `/api/browser-auth/*`access/refresh token 仅写入 Secure、HttpOnly Cookie响应体不返回 token不安全方法必须通过同源 Origin 与双提交 CSRF 校验。`/api/auth/*` Bearer 契约继续供小程序、原生客户端和服务调用。
## Host 与 tenant 解析
Host 是认证上下文,不是普通参数。`TenantResolutionMiddleware` 在 Authentication 前执行。
| 请求入口 | 租户上下文 | 允许 realm | 默认结果 |
| --- | --- | --- | --- |
| Platform Host | 无租户 | platform白名单入口可用 tenantCode 引导 tenant 登录 | 继续 |
| Active 租户 Host | Host 绑定租户 | tenant | 继续 |
| 租户 Host + 其他 tenantCode/header | 冲突 | 无 | 403 |
| Platform Host + platform realm + tenantCode | 非法混合 | 无 | 400 |
| 未知 Host | 无 | 无 | 非豁免路径 404 |
| Pending/禁用域名 | 无 | 无 | 404 |
规则:
- 自定义域名不接受 `tenantCode``host` query 或客户端转发头覆盖。
- tenant JWT 的 `tid` 必须与 Host 解析租户一致。
- platform JWT 不能访问租户 Host。
- 平台 Host 上的租户登录引导才允许受控使用 `tenantCode`
- 只接受可信代理写入的 Forwarded Headers直连客户端伪造无效。
## RBAC、菜单与 DataScope
租户后台与平台后台角色分离:
- 租户角色、权限、菜单、用户角色绑定都带租户上下文。
- 平台角色不带租户键,不能自动读取租户业务数据。
- 菜单只决定 UI bootstrap 展示,不作为 API 授权依据。
- 后台 API 必须声明明确 permission高风险写操作必须记录审计。
- UI bootstrap 只返回“有效 permission 推导菜单”与有效 Capability 的交集;租户不能绑定当前无权使用的模块权限。
- Trial/Active 且在有效期内可写PastDue/Cancelled/Expired 仅允许已有权益模块的历史读取。
DataScope
- `All`:当前租户内该模块全部资源。
- `Restricted`:按 region/class/owner 等资源关系过滤。
- `Self`:只允许当前用户关联资源。
- 无法可靠表达资源关系的模块只允许 `All`,不能退化为仅按 tenant 查询。
## 短信验证码
- 验证码生成、哈希、频控、过期和校验由自有业务服务负责。
- `ISmsProvider` 只负责发送。
- 发送失败必须记录失败状态,不能留下可验证验证码。
- 登录、绑定、找回密码等场景使用独立 purpose 和频控键。
- Redis Lua 同时执行跨实例 IP、账号、租户、手机号和 purpose 窗口计数key 只使用 GUID 或不可逆哈希。
- Redis 不可用时密码尝试、短信发送和短信校验失败关闭;普通授权请求仍直接查询 PostgreSQL。
## 可靠安全事件
- `Tiku.Contracts` 只包含版本化 DTO不引用 EF、HTTP 或 Provider SDK。
- API 使用 MassTransit EF Bus OutboxWorker consumer 使用 EF inbox/outbox业务变更、审计和消息由同一 DbContext 提交。
- RabbitMQ 消息只负责非权威失效版本、菜单刷新和下游通知成员、租户、Session 或套餐失效不等待 consumer。
- 即时 `BackgroundJob``BackgroundJobRequestedV1` Consumer 执行;延时任务和失败后的定时重试继续由数据库调度器处理,同一即时任务不会同时进入两种消费路径。业务 handler 必须使用受审计 System Scope 提供的 scoped `DbContext`
- System Scope 只能通过完整 `SystemScopeRequest` 创建;成功路径将 entered 审计、跨租户业务写入和 completed 审计放入同一 PostgreSQL 事务。
## 审计与错误
必须落审计:
- 登录、刷新重放、logout-all、强制改密
- 角色、权限、成员状态、租户状态、Provider 配置、支付运营动作;
- System Scope 和跨租户平台操作。
错误响应:
- 401未认证或 token/session 无效。
- 403已认证但 realm、tenant、permission、DataScope 或套餐能力不满足。
- 404未知 Host、不可见资源或需要隐藏存在性的资源。
- 响应不得泄露完整手机号、openId、邮箱、密钥、支付账号或内部 provider payload。
## 生产配置清单
- 正式 `PlatformHosts`
- 可信代理地址和网络 ACL。
- 非通配 `AllowedHosts`
- JWT issuer、audience、当前 `KeyId`、RSA 私钥和旧公钥集合。
- Data Protection 证书。
- CORS 明确 Origin。
- Redis 7.2+ 连接串Production 缺失时拒绝启动。
- RabbitMQ 4.x Host、virtual host 与凭据Production 缺失时拒绝启动。
- 默认镜像不依赖 `x-delayed-message` 插件Consumer 使用有限即时重试,延时业务重试落回 PostgreSQL `RunAfter`
- 公网只暴露覆盖 Forwarded Headers 的可信网关API ACL 只允许该网关访问。
- Secret encryption key。
- 短信、对象存储、支付、通知和 AI provider 只通过租户 Provider 配置读取密钥。
待补强事项见 [认证与授权待补强清单](authentication-authorization-hardening-plan.md)。

View File

@@ -1,9 +0,0 @@
# Endpoint authorization manifest
Controller 授权面由 `AuthorizationManifestTests` 按 HTTP method、route、controller/action、匿名标记和 policy 生成稳定摘要。
`EndpointAuthorizationMetadataConvention` 为全部非匿名 Controller endpoint 生成 realm、module、permission、CapabilityOperation、All-only DataScope 与 audit action 元数据,测试从运行时 `EndpointDataSource` 验证覆盖。新增、删除或修改 Action 时摘要测试必须失败,评审者确认元数据后才能更新 count/hash。
该清单是防止接口绕过评审的变更门禁;实际授权事实仍来自 PostgreSQL permission、Capability 和 DataScope不能用摘要替代运行时校验。
- Action 数量332
- SHA-256`a80fe477ba3021625e17c9fc639e5109bab678178f8024a51c3c732bf5a46d3f`

View File

@@ -0,0 +1,124 @@
# 系统架构与业务边界
本文描述当前仓库的实际代码结构和运行时职责。接口路径、DTO 和响应模型以运行时 OpenAPI 为准。
## 分层与依赖
```text
+------------------+
| Tiku.Contracts |
+--------^---------+
|
+-----------+ +---------------+---------------+
| Tiku.Api | | Tiku.Worker / Tiku.DbMigrator |
+-----+-----+ +---------------+---------------+
| |
+-------------+-------------+
v
+---------------------+
| Tiku.Infrastructure |
+----------+----------+
v
+---------------------+
| Tiku.Application |
+----------+----------+
v
+---------------------+
| Tiku.Domain |
+---------------------+
```
- `Tiku.Domain` 保存领域实体、枚举和基础类型。除 Identity stores 抽象外,不依赖持久化或 Provider SDK。
- `Tiku.Application` 定义用例契约、Provider 接口、安全上下文和业务目录,依赖 Domain。
- `Tiku.Infrastructure` 实现 EF Core、PostgreSQL、Identity、外部 Provider、消息和后台任务依赖 Application、Domain 与 Contracts。
- `Tiku.Contracts` 保存 API 与 Worker 使用的版本化消息 DTO不引用 HTTP、EF Core 或 Provider SDK。
- `Tiku.Api``Tiku.Worker``Tiku.DbMigrator` 是独立运行入口。
## 运行时组件
### API
`Tiku.Api/Program.cs` 只负责组合服务、构建应用和启用请求管线。管线的关键顺序是:
```text
Forwarded Headers
-> HTTPS / 压缩 / 静态文件
-> Routing / CORS
-> Host 租户解析
-> 浏览器 CSRF
-> JWT 认证 / 认证专用限流 / 全局限流
-> 当前用户上下文 / 授权
-> SaaS Feature 校验
-> Output Cache
-> Controllers
```
OpenAPI 和 Scalar 只在 Development 映射。平台管理静态文件由 `Tiku.Api/wwwroot` 同源托管,默认入口是 `/platform-admin/`
### DbMigrator
`Tiku.DbMigrator` 是唯一迁移入口,执行顺序为:
1. 解析 `ConnectionStrings:Database``DATABASE_URL`
2. 进入带审计原因的 System Scope。
3. 执行 `Database.MigrateAsync()`
4. seed 内置 SaaS Feature、PermissionModule、BackendPermission 和 BackendMenu 目录。
5. Development 全新数据库自动 seed 平台管理员;非 Development 仅在显式传入 `--bootstrap-platform-admin` 时创建管理员。
API 和 Worker 都不自动迁移数据库。
### Worker
`Tiku.Worker` 当前注册四个独立 Hosted Service
| Worker | 周期 | 当前职责 |
| --- | --- | --- |
| `TenantDomainWorker` | `TenantDomains:PollSeconds`,限制为 103600 秒 | 校验自定义域名 CNAME/TXT调用网关 TLS 接口并失效租户缓存 |
| `SaasSubscriptionWorker` | 60 秒 | 处理到期、宽限期等 SaaS 订阅生命周期 |
| `FeatureUsageWorker` | `FeatureUsageReconciliation:IntervalMinutes`,限制为 11440 分钟 | 按真实业务数据校准租户 Feature 用量 |
| `BackgroundJobsWorker` | 2 秒4 个分区 | 租约处理 PostgreSQL 中的延时/待执行后台任务;未配置 RabbitMQ 时也处理即时任务 |
后台任务当前支持 `content_import``content_export``statistics_aggregation``commerce_reconciliation``tenant_domain_recheck``asset_security_scan` 会明确失败,直到配置实际扫描 Provider不能把它描述为已接通扫描服务。
配置 RabbitMQ 后,即时安全事件和后台任务请求使用 MassTransitAPI 使用 EF Bus OutboxWorker Consumer 使用 EF inbox/outbox。延时任务仍由 PostgreSQL `RunAfter` 和租约 Worker 处理。
## 数据与持久化
- 数据库使用标准 PostgreSQL普通 schema 由 EF Core entity、Fluent Configuration 和 Migration 管理。
- 当前模型启用 `citext``ltree``pg_trgm` 扩展,并统一映射为 `snake_case`
- Data Protection key ring 由 API 持久化到 PostgreSQL非 Development 必须使用 X509 证书保护。
- MassTransit inbox/outbox 表与业务表处于同一 `TikuDbContext`
- PostgreSQL 不启用 RLS租户隔离由应用和数据库多层共同保证详见[认证、授权与租户隔离](security-and-tenancy.md)。
## 当前业务模块
### 平台端
- 租户、Owner、域名、状态、员工、角色和审计告警。
- 平台公共题库、分类节点、题目、导入和资源上传。
- SaaS Feature、额度定义、套餐版本、报价、订单、支付、退款、订阅、发票和催缴。
- 平台级 CRM、短信渠道/模板和支付应用配置。
### 租户端
- 员工、角色、权限、菜单、DataScope 和租户设置。
- 私有题库、公共题库引用、内容目录、词汇、手册、视频、分数线、站点内容、导入导出和资源。
- 学生、班级、CRM 跟进、监管规则、报表和审计。
- 学生商城、订单、支付、退款、优惠券、积分、推广和分佣。
- 租户 SaaS 目录、账务、订阅、用量、发票和 onboarding 状态。
- 身份、短信、对象存储、支付、通知和 AI 的租户 Provider 配置边界。
### 学生端
- Host 对应的运行时品牌、导航、Feature 和登录方式 bootstrap。
- 账号登录、个人资料、通知、签到和积分。
- 题目目录、练习会话、作答、收藏、错题、视频播放和进度。
- 学生商品、订单、支付、优惠券、权益和推广关系。
是否存在某个具体操作,应以 Controller 和 OpenAPI 为准,不能仅凭本节的模块名称推断。
## 外部服务边界
Application 通过接口表达身份、短信、对象存储、支付、通知、域名和 AI 能力Infrastructure 当前包含自托管身份、阿里云短信/OSS、微信、支付宝、站内通知、DNS JSON 查询和 HTTP 网关实现。
租户级 Provider 元数据和密钥分别存入 `TenantExternalProvider``TenantSecret`。密钥由 32 字节 master key 加密API 不应把明文、`SecretRef` 或 Provider 内部 payload 返回给客户端。

View File

@@ -1,230 +0,0 @@
# SaaS 题库产品边界与后续接口路线
本文档定义平台端、租户端、学生端的目标边界,以及下一阶段接口开发顺序。当前 ASP.NET Core 后端是实现基线;旧 NestJS 和 `tiki-web` 只用于核对业务行为yudao 只用于参考套餐、商城、支付和后台运营的模块划分。
## 当前判断
现有后端已经具备继续开发的基础Host 租户解析、强租户隔离、共享与私有题库、RBAC、Provider 解耦、租户前端运行时配置、学生练习闭环、交易基础和 Worker 基座均已落地。
第九阶段已经完成 SaaS 产品与交付闭环。当前主要缺口是:
- 教师发布作业、考试、批阅和查看班级结果的教学闭环。
- Provider 自助配置、公共题库运营和高流量查询读模型仍需完善。
## 三端边界
### 平台端
平台端是 SaaS 控制面,负责:
- 租户、租户 Owner、状态、域名和生命周期。
- SaaS 业务模块、套餐、附加包、价格和额度。
- 租户订阅、SaaS 订单、支付、退款、账单、发票、催缴和用量。
- 平台员工、平台角色、平台权限、审计和告警。
- 公共题库、公共分类、题目版本、发布和反馈质量运营。
- 平台自身的收款 Provider不使用租户配置的学生商城支付账号。
### 租户端
租户端是机构控制面,负责:
- 员工、自定义角色、权限、班级、学生和数据范围。
- 私有题库、公共题库消费、分类扩展、组卷、导入和导出。
- 作业、考试、每日一练、批阅和教学报告。
- 品牌、主题、导航、首页模块和自定义域名。
- 身份、SMS、对象存储、学生商城支付、通知和 AI Provider。
- 学生商品、会员、优惠券、激活码、积分、CRM、推广和分佣。
- 本租户 SaaS 订阅、账单、用量、续费和升级。
### 学生端
学生端是租户域名下的数据面,负责:
- 根据 Host 获取租户品牌、功能、导航和允许的登录方式。
- 登录、绑定、个人资料和通知。
- 题库、练习、考试、作业、错题、收藏和学习报告。
- 词汇、知识手册、视频、分数线等可选内容模块。
- 租户自己的学生商城、订单、支付、优惠券、积分和权益。
## 套餐能力与权限分层
不能用一套“模块”同时表达套餐、权限和菜单。目标模型固定为:
| 概念 | 用途 |
| --- | --- |
| `SaaSFeature` | 平台可销售的业务能力 |
| `SaasOfferingVersionFeature` | 不可变套餐版本包含哪些业务能力 |
| `SaasOfferingVersionLimit` | 套餐版本的员工、学生、题目、存储、导出和 AI 额度 |
| `PermissionModule` | 后台权限页面的业务分组 |
| `BackendPermission` | `view/create/update/import/export/approve/retry` 等操作权限 |
| `BackendMenu` | 根据有效权限生成的前端导航,不作为鉴权依据 |
建议的可售卖能力包括:
- `question_bank.private`
- `learning.practice`
- `learning.assignment`
- `learning.exam`
- `content.vocabulary`
- `content.handbook`
- `content.video`
- `content.scoreline`
- `marketing.site_content`
- `student.management`
- `commerce.student_store`
- `crm.followup`
- `growth.referral_commission`
- `ai.teacher_assistant`
身份安全、角色管理、账务中心和续费入口属于核心能力。即使套餐过期,也不能阻止租户查看账单、配置管理员或完成续费。
每次受保护的业务请求必须同时满足:
```text
租户有效
+ 订阅状态允许当前读写操作
+ 套餐或附加包包含业务能力
+ 未超过对应额度
+ 当前角色具有操作权限
+ DataScope 允许访问目标数据
```
## 双交易域
平台 SaaS 商城和租户学生商城必须是两个独立边界。
### PlatformBilling
平台向租户收费,包含:
- SaaS 套餐、附加包和报价。
- SaaS 订单、支付、退款、订阅、账单和发票。
- 平台收款 Provider 和平台支付回调。
- 租户用量、超额计费、额度预警和催缴。
### TenantCommerce
租户向学生收费,包含:
- SVIP、课程资料和其他学生商品。
- 学生订单、支付、退款、优惠券、激活码和权益。
- 当前租户配置的支付 Provider 和回调。
两类订单、支付账号、回调地址、审计和对账不得共用业务表或服务。
## 已完成的 SaaS 商城与交付接口
### 平台 SaaS 商城
- 平台模块、额度定义、基础套餐、附加包和不可变版本统一在 `/api/platform-admin/saas/**`
- 租户目录、报价、下单、支付、订阅变更、续费、取消、用量和发票统一在 `/api/tenant-billing/**`
- 人工、微信和支付宝平台收款使用平台主体 Provider订单和回调与学生商城分离。
- `/api/tenant-onboarding/status` 汇总 Owner、订阅、域名、登录方式、Provider 和前端发布状态。
租户自助账务接口建议统一在 `/api/tenant-billing/**`
```text
GET /api/tenant-billing/catalog
POST /api/tenant-billing/quotes
POST /api/tenant-billing/orders
POST /api/tenant-billing/payments
GET /api/tenant-billing/orders/{orderNo}
GET /api/tenant-billing/subscription
POST /api/tenant-billing/subscription/change
POST /api/tenant-billing/subscription/renew
POST /api/tenant-billing/subscription/cancel
GET /api/tenant-billing/usage
GET /api/tenant-billing/invoices
```
### Provider 自助管理
统一使用 `TenantExternalProvider + TenantSecret`,补齐:
```text
GET /api/tenant-admin/providers
PUT /api/tenant-admin/providers
POST /api/tenant-admin/providers/test
POST /api/tenant-admin/providers/activate
POST /api/tenant-admin/providers/disable
PUT /api/tenant-admin/providers/secrets
POST /api/tenant-admin/providers/secrets/rotate
```
运行时 bootstrap 需要增加脱敏的登录方式配置,不能返回 SecretRef、密钥或第三方内部配置。
### 教师教学闭环
- 作业、考试、每日一练的创建和发布。
- 发布目标:班级、学生组、指定学生。
- 开始时间、截止时间、限时、补交和自动交卷规则。
- 学生答题草稿、断点续答和最终提交。
- 客观题自动批改,主观题教师批阅、复核和评语。
- 完成率、成绩分布、薄弱知识点和学生明细。
- 试卷、成绩、每日一练和战报导出。
现有 `PracticeBlueprint``PracticeSession` 可以作为题目装配及作答底座,但不能代替教师发布对象和班级任务状态。
### 平台公共题库运营
- 公共题库和公共分类主干管理。
- 题目草稿、审核、发布、撤回和版本对比。
- 重复题检测、反馈汇总和人工复核。
- 使用量、错误率、反馈率和版本采用情况。
- 已发布旧版本禁止物理删除。
### 查询性能和读模型
- 普通列表采用游标分页和稳定排序,禁止默认返回大集合。
- 题目、院校和知识点搜索优先使用 PostgreSQL trigram/全文索引。
- 首页、排行榜和运营看板使用聚合表或异步投影。
- 公共目录和 runtime bootstrap 使用 Redis 缓存并主动失效。
- 导入、导出、统计、资源扫描和对账进入 Worker。
- 使用 OpenTelemetry 观测慢查询、接口耗时、缓存命中和 Worker 延迟。
## 实施顺序
### 9A9C已完成
- `SaasFeature``PermissionModule``BackendPermission``BackendMenu` 已分层。
- `SaasOfferingVersion` 发布后由 Application 和 PostgreSQL guard 双重禁止修改。
- `IFeatureAccessService` 统一处理租户、订阅、Feature、覆盖、额度与权限过滤。
- PlatformBilling 与 TenantCommerce 使用独立订单、支付、回调和 Provider 配置。
- 平台创建租户及 Owner 后,租户可完成购买、开通和 onboarding。
### 9D教师教学与考试
- 作业、考试、班级发布、批阅和教学报告。
- 智能组卷、每日一练、PDF 命题和战报持久化。
- 导出任务通过 Worker 和对象存储交付。
### 9E学生端与性能治理
- runtime 登录选项、手机号绑定和找回密码。
- 作业/考试中心、断点续答和报告。
- 根据产品决定是否迁移备考时间线和择校功能。
- 完成分页、索引、缓存、聚合投影和性能基线测试。
### 9FAI 独立阶段
- 面向租户教师的基础对话和后续 Function Call。
- AI 题目反馈审核,只输出建议和人工复核标记。
- Semantic Kernel 仅存在于 Infrastructure。
- 租户 API Key 保存到 `TenantSecret`
- AI 调用量、成本和额度进入 SaaS 计量体系。
## 验收原则
- 套餐未包含的功能不能分配权限、不能显示菜单、不能调用 API、不能由 Worker 绕过执行。
- 平台角色、租户角色和学生身份不能跨 realm 使用。
- 租户 A 不能读取或修改租户 B 的配置、学生、题库、订单和 Provider。
- 平台 SaaS 支付与租户学生支付使用不同配置、订单域和回调链路。
- 套餐过期后业务写入受限,但账务、续费、安全和历史数据仍可访问。
- 高风险操作、支付状态变化、订阅变化和 Provider 变化都有审计记录。
- 关键查询在接近生产的数据量下验证执行计划、分页稳定性和响应时间。
## 参考边界
- 旧 NestJS核对已有接口语义、状态机和异常行为不要求保留旧 URL。
- `tiki-web`:参考已实际使用的刷题、词汇、手册、商城、营销、教研和运营功能,不复制 PocketBase 查询方式。
- yudao参考租户套餐、商城订单、支付、退款、权限和审计的模块拆分不照搬菜单 ID 套餐模型或 Java 运行时。

View File

@@ -0,0 +1,124 @@
# 认证、授权与租户隔离
本文说明当前请求实际经过的安全边界。新增接口、实体或后台任务时,必须保持这些边界闭合。
## 认证入口
API 支持两组认证接口:
- `/api/auth/**` 返回 access token 与 refresh token适合 Bearer 客户端。
- `/api/browser-auth/**` 把 token 写入 HttpOnly Cookie适合同源浏览器客户端。
当前登录方式:
- 平台账号:账号/密码。
- 租户账号:手机号/密码、手机号/短信验证码。
- 租户可配置微信网页授权和微信小程序授权。
主要流程包括短信发送、密码登录、短信登录、微信登录、refresh、logout、logout-all 和首次登录强制改密。具体请求与响应字段以 Scalar 为准。
密码至少 8 位,并必须同时包含字母和数字。连续 5 次失败触发 15 分钟 Identity lockout。短信验证码由本服务生成和哈希发送 Provider 只负责投递;验证码校验最多允许 5 次尝试。
## JWT 与 Session
- access token 使用 RSA SHA-256 签名,默认有效期 15 分钟。
- refresh token 默认有效期 30 天,服务端只保存哈希。
- JWT 必须包含用户、Session、`jti`、签发时间和 `realm`;租户 realm 还必须包含租户 ID。
- 每次 JWT 认证都会核对数据库 Session、用户/成员状态、安全版本和租户上下文,不把 JWT 声明当作永久授权事实。
- refresh token 轮换并检测重放logout 撤销当前 Sessionlogout-all 撤销用户全部 Session。
- 平台 token 只能在平台 Host 使用;租户 token 必须与 Host 或允许路径上的 tenant code 解析结果一致。上下文冲突返回 401不允许静默切换租户。
Production 必须显式配置 JWT `KeyId`、私钥和验证公钥集合,不能使用 Development 临时密钥。
## 浏览器 Cookie 与 CSRF
Browser Auth 使用 access、refresh 和 CSRF Cookie
- access/refresh Cookie 为 HttpOnly。
- Bearer handler 只会在同源浏览器请求中回退读取 access Cookie显式 `Authorization` header 优先。
- 使用 Cookie 的非安全方法必须通过 `BrowserCsrfMiddleware` 的 Origin/Referer 与 CSRF token 校验。
- 跨源浏览器使用必须同时正确配置 `Cors``BrowserAuth:AllowedOrigins`;允许凭据时不能使用通配 Origin。
非浏览器客户端应使用 Bearer token不应复制浏览器 Cookie 流程。
## Realm、Permission、Feature 与 DataScope
每个受保护操作可能同时经过四层判断:
```text
Realmplatform / tenant
+ BackendPermission操作权限
+ SaaSFeature套餐能力
+ DataScope资源范围
```
- Realm 防止平台身份、租户员工和学生身份跨授权域复用。
- `BackendPermission` 控制 `view/manage/read/write/operate` 等操作。
- `SaaSFeature` 是固定代码目录当前包括后台基础、私有题库、练习、作业、考试、词汇、手册、视频、分数线、站点内容、学生管理、学生商城、CRM、推广分佣和教师 AI。
- 菜单由有效 Permission 与 Feature 共同推导,只用于 UI bootstrap不是 API 授权依据。
- DataScope 支持 `All``Restricted``Self`。无法提供可靠资源 predicate 时返回空查询,不能退化为“当前租户全部数据”。
- 套餐状态、Feature override 和额度使用量来自 PostgreSQLRedis 只用于失效通知和缓存,不能成为授权真相。
Controller 默认受 Fallback Policy 保护,匿名接口必须显式标记 `[AllowAnonymous]``EndpointAuthorizationMetadataConvention` 为非匿名 Controller endpoint 补充 realm、module、permission、Feature 操作、All-only DataScope 和审计元数据,集成测试从运行时 `EndpointDataSource` 验证覆盖。
## Host 与租户上下文
`TenantResolutionMiddleware` 在认证前解析租户:
1. 对平台 Host不默认建立租户上下文。
2. 对非平台 Host按启用的租户域名查找租户未匹配且不属于豁免路径时返回 404。
3. 只有 `TenantCodePathPrefixes` 明确允许的路径,才能在平台 Host 使用 `x-tenant-code``tenantCode` 解析租户。
4. JWT tenant ID 与已解析租户必须一致,否则认证失败。
Development 默认平台 Host 是 `localhost``127.0.0.1`。Production 启动校验要求:
- 至少一个非 loopback 的正式平台 Host
- 非通配 `AllowedHosts`
- 至少一个合法的 `TrustedProxyAddresses`
- 仅信任一跳且来源位于可信代理列表的 `X-Forwarded-For/Host/Proto`
客户端不得通过任意 header、query 或转发头绕过以上路径和可信代理限制。
## 数据库租户隔离
当前 PostgreSQL 连接角色不依赖 RLS。租户隔离由以下机制共同完成
### 查询
`TikuDbContext` 自动为所有包含 `TenantId` 的实体应用 Query Filter。普通请求只有在租户上下文已解析且 ID 匹配时可见System Scope 才能绕过。
模型启动校验会拒绝:
-`TenantId` 但未实现 `ITenantOwned` 的实体;
- 缺少租户 Query Filter 的实体;
- 未包含 `TenantId` 且未显式声明全局唯一的 unique index
- 租户实体之间未使用租户限定 principal key 的外键。
### 写入
`TenantIsolationSaveChangesInterceptor` 检查新增、修改和删除实体的租户所有权,防止普通请求写入其他租户或伪造 `TenantId`。Controller 与 Service 不应接受可任意填写的租户 ID、owner tenant ID、bucket 或 Secret 引用。
### 数据库约束
能用 FK、unique 和 check 表达的规则优先使用 EF 配置。当前集中 PostgreSQL guard 额外保证:
- `TenantQuestionReference` 只能指向平台公共题或当前租户私题,且 source 必须匹配所有者类型。
- `TaxonomyNode` 的父节点只能属于平台主体或当前租户。
- 已发布 SaaS 套餐版本及其 Feature/额度清单不可修改,并校验订阅与订单快照的一致性。
这些 guard 由 Migration helper 统一安装和移除,不允许在多份 Migration 中复制 SQL。
## System Scope 与可靠事件
跨租户 Worker、迁移、seed 和平台级后台操作必须通过 `ITenantContextInitializer.InitializeSystem` 或受审计的 `ITenantExecutionScope` 进入 System Scope并提供明确原因。业务代码不得直接关闭 Query Filter。
配置 RabbitMQ 时:
- API 使用 EF Bus Outbox把业务写入、审计和消息放在同一数据库事务边界。
- Worker Consumer 使用 EF inbox/outbox 和有限即时重试。
- Session、成员、租户和套餐状态始终从 PostgreSQL 重新校验,不等待消息消费后才失效。
- 延时/定时重试使用 PostgreSQL `RunAfter`,不依赖 RabbitMQ delayed-message 插件。
## 安全配置门禁
Production 还会在启动时验证 Redis、RabbitMQ、Data Protection 证书、租户 Secret master key、短信 pepper、CORS 和外部服务配置。完整配置入口见[配置与后台任务](../operations.md)。