15 KiB
TIKU SaaS 认证与授权补强计划
状态:待实施
制定日期:2026-07-28
适用范围:Tiku.Api、Tiku.Application、Tiku.Infrastructure、Tiku.Worker 及相关 PostgreSQL 集成测试。
本文档是在现有《TIKU SaaS 认证、授权与 Host 安全策略》基础上制定的实施计划。现有体系已经具备 ASP.NET Core Policy-based Authorization、数据库 Session、tenant/platform realm、数据库 RBAC、MFA、DataScope、EF Core tenant query filter、写入拦截器和 PostgreSQL 约束。本计划不推翻这些边界,而是按风险优先级补齐生产安全、SaaS 套餐授权、最小权限验证、特权审计和客户端认证规范。
1. 目标与原则
补强后的每次授权应形成以下闭环:
User Active
+ Session Active
+ Host / Realm / Tenant 一致
+ Tenant Active
+ Membership Active
+ Subscription 有效
+ 套餐包含目标模块
+ Operation Permission
+ DataScope / Resource Scope
+ 敏感操作 MFA
实施遵循以下原则:
- 先处理可导致安全边界绕过或权限生命周期失效的问题,再扩展产品能力。
- 所有安全配置默认 fail-closed,不能只依赖部署文档提醒。
- JWT 继续只表达已认证会话,不承载可直接授权的角色和权限。
- 前端菜单、feature flag 和页面隐藏永远不能代替 API 授权。
- 列表、详情、写入、批量、导出和异步任务使用一致的数据范围。
- 每个阶段独立实现、独立测试、独立提交;未通过本阶段验收不得进入下一阶段。
2. 优先级与交付顺序
| 优先级 | 阶段 | 目标 | 预计工作量 | 发布要求 |
|---|---|---|---|---|
| P0 | 可信代理与 Host 边界 | 阻止伪造 Forwarded Host 改变安全上下文 | 1~2 天 | 生产前必须完成 |
| P0 | 成员禁用生命周期 | 禁止外部登录恢复 Disabled 成员 | 0.5~1 天 | 生产前必须完成 |
| P1 | SaaS Capability 授权 | 统一套餐、模块、权限、DataScope 和 MFA | 3~5 天 | SaaS 商业化前必须完成 |
| P1 | 接口最小权限审计 | 防止过宽 policy 和 DataScope 漏检 | 2~4 天 | 新后台全面接入前完成 |
| P1 | System Scope 审计 | 约束并持久化跨租户特权操作 | 2~3 天 | Worker/平台操作上线前完成 |
| P2 | Token、隐私与协议规范 | 降低浏览器、PII 和自定义协议风险 | 3~7 天 | 正式客户端规模化前完成 |
推荐交付顺序:
可信代理
-> Disabled 成员
-> Capability / 套餐授权
-> 接口权限清单与 DataScope
-> System Scope 审计
-> 浏览器 Token、PII 和 OIDC 演进
3. P0:收紧可信代理和 Host 边界
3.1 风险
系统把 Host 作为 tenant/platform realm 的安全上下文,但当前 Forwarded Headers 配置会清空框架默认的 KnownProxies 和 KnownIPNetworks。如果生产环境没有提供有效 TrustedProxyAddresses,应用不会以 fail-closed 方式拒绝非可信来源提供的 X-Forwarded-Host。
3.2 实施范围
- 为
TenantResolutionOptions增加生产配置校验:- Production 必须配置至少一个正式
PlatformHosts。 - Production 必须配置至少一个合法
TrustedProxyAddresses。 - Production 不得只保留
localhost或127.0.0.1作为 Platform Host。 - 代理地址无效时应用启动失败。
- Production 必须配置至少一个正式
- 调整
ForwardedHeadersOptions:- 只有存在合法可信代理配置时,才替换框架默认 proxy/network 限制。
- 保持
ForwardLimit = 1。 - 显式配置
AllowedHosts。 - 只处理部署所需的
X-Forwarded-For、X-Forwarded-Host和X-Forwarded-Proto。
- 收紧 Host 配置:
- 生产
AllowedHosts不允许使用*。 - Platform Host、租户公共主域名和允许的网关 Host 必须形成可审计配置。
- 生产
- 固化网关契约:
- 网关覆盖客户端提供的 Forwarded Headers。
- API 监听端口不可绕过网关直接暴露公网。
- 网络 ACL 只允许受信网关访问 API。
3.3 自动化验收
- 未受信来源伪造 Platform Host,不能进入 platform realm。
- 未受信来源伪造其他租户 Host,不能改变 tenant context。
- 受信代理转发 Active 租户域名,可以解析正确租户。
- Tenant A Host 携带 Tenant B token,返回 403。
- 未知 Host 的非豁免路径返回 404。
- Production 缺少 Platform Host 或可信代理时启动失败。
- 伪造 Forwarded Headers 的测试必须经过完整 HTTP middleware pipeline,不能只单测
TenantResolutionMiddleware。
3.4 建议提交
fix(security): fail closed on forwarded host trust
4. P0:修正 Disabled 成员生命周期
4.1 固定状态语义
| Membership 状态 | 登录行为 | 状态改变方式 |
|---|---|---|
| Active | 允许继续认证 | 正常业务流程 |
| Invited | 不得静默激活 | 显式接受邀请或管理员确认 |
| Disabled | 所有登录方式拒绝 | 仅管理员显式恢复 |
| 不存在 | 根据租户自注册策略决定 | 创建新 Student 或拒绝 |
4.2 实施范围
- 外部身份登录只能创建从未存在过的 Student membership,不能恢复历史 membership。
- 查询到 Disabled membership 时返回统一租户访问拒绝,不能改回 Active。
- 查询到 Invited membership 时进入显式邀请接受流程,不能由微信登录静默激活。
- 建议增加租户级注册策略:
AllowStudentSelfRegistrationAllowedSelfRegistrationProvidersRequireRegistrationApproval
- 成员恢复必须复用管理员权限、DataScope 和审计,不允许隐藏在登录流程里。
4.3 自动化验收
- Disabled Student 不能通过微信 Web 或小程序登录恢复状态。
- Disabled Student 的旧 access token 和 refresh token 继续立即失效。
- Invited 成员不会被外部身份登录静默转为 Active。
- 允许自注册时,首次微信登录可以创建 Student membership。
- 关闭自注册时,首次微信登录被拒绝。
- 管理员显式恢复并写入审计后,成员才能重新登录。
4.4 建议提交
fix(auth): preserve disabled tenant membership state
5. P1:建立统一 SaaS Capability 授权层
5.1 目标
现有 RBAC 继续负责“用户是否有操作权限”,新增 Capability 层负责“租户当前是否购买、启用并可使用该能力”。两者必须同时成功。
5.2 Application 抽象
建议在 Application 层新增只读上下文:
public interface ICurrentTenantCapabilityContext
{
Task<CurrentTenantCapabilitySnapshot> GetAsync(
CancellationToken cancellationToken = default);
}
Snapshot 至少表达:
TenantIdSubscriptionStatusSubscriptionExpiresAtEnabledModulesFeatureFlagsUsageLimitsIsReadOnlyDenialReason
建议增加以下 Authorization Requirement:
TenantSubscriptionRequirementTenantModuleRequirement(moduleCode)TenantUsageRequirement(resourceCode)
5.3 Policy 组合
Controller 不得分别手工检查套餐和权限。现有 permission policy 应由统一注册器或自定义 policy provider 组合:
tenant:content:manage
= CurrentTenantMember
+ ActiveSubscription
+ TenantModule(content)
+ TenantPermission(tenant:content:manage)
+ MFA
平台权限和租户权限继续保持 realm 隔离。平台管理员不能因为拥有平台权限而自动读取租户业务数据,跨租户操作必须进入受控 System Scope。
5.4 订阅状态语义
实施前固定以下默认规则,产品有不同要求时必须在代码和测试中显式调整:
| 订阅状态 | 历史读取 | 新增业务数据 | 后台配置 |
|---|---|---|---|
| Trial | 允许 | 允许,受额度限制 | 允许 |
| Active | 允许 | 允许 | 允许 |
| PastDue | 允许 | 默认拒绝或只读 | 仅账单相关 |
| Cancelled | 允许导出和历史查询 | 拒绝 | 仅账单和迁出 |
| Expired | 按保留期只读 | 拒绝 | 仅恢复订阅 |
租户不存在、模块未购买、订阅失效和额度耗尽应使用不同内部错误码,不能全部退化成同一个未找到响应。
5.5 自动化验收
- 有 permission 但套餐不含模块,返回 403。
- 套餐包含模块但没有 permission,返回 403。
- 套餐和 permission 都有效时允许访问。
- PastDue、Cancelled 或 Expired 不能创建新的受限资源。
- 修改套餐或模块后,旧 access token 不需要等待过期即可失去能力。
- 前端 feature flag 关闭或开启都不能绕过后端 module requirement。
- 公共题库订阅规则继续通过统一 Capability 或受控领域 policy 执行。
5.6 建议提交
feat(authz): enforce tenant subscription capabilities
6. P1:接口最小权限与 DataScope 审计
6.1 接口授权清单
建立代码化或可由测试读取的 endpoint authorization manifest,每个接口至少记录:
- HTTP method 与 route
- tenant/platform realm
- 是否允许匿名
- module code
- permission code
- DataScope 策略
- 是否要求 MFA
- 审计 action
示例:
| 路由 | Realm | 模块 | Permission | DataScope |
|---|---|---|---|---|
POST /tenant/students |
tenant | student | tenant:student:manage |
Region/Class |
PUT /tenant/providers |
tenant | provider | tenant:provider:manage |
All-only |
GET /platform/tenants |
platform | platform-admin | platform:tenant:manage |
Platform |
POST /content/questions |
tenant | content | tenant:content:manage |
Owner/Region |
6.2 架构测试
- 后台写接口不得只使用
[Authorize]。 - 后台管理接口不得只使用
CurrentTenantMember。 - tenant Controller 不得引用 platform permission,反之亦然。
- permission 必须属于 endpoint 声明的 module。
- All-only 操作必须显式声明,不能隐式放宽。
[AllowAnonymous]只能出现在审核后的白名单路由。- 新增 Controller action 未进入 manifest 时测试失败。
TenantResourceAccessRequirement不能单独代替 operation permission 和 MFA。
6.3 DataScope 测试矩阵
每类资源至少覆盖:
- 列表过滤
- 单条详情
- 创建和目标归属验证
- 更新
- 删除
- 批量操作
- 导出
- Worker 或后台任务异步执行
必须证明“列表不可见的资源不能通过已知 ID 更新、删除或导出”。无法可靠映射 owner、region 或 class 的资源继续采用 All-only fail-closed。
6.4 交付方式
按模块拆分窄提交,例如 student、content、commerce、CRM、platform-admin,避免一次性修改所有 Controller 和 Service。
7. P1:收紧 System Scope 并持久化审计
7.1 结构化上下文
用结构化请求代替单一 reason 字符串,至少包含:
OperationTargetTenantIdActorUserId或 Worker identityJobIdCorrelationIdReasonRequestedAt
7.2 持久化审计
System Scope 进入和退出都写数据库:
system_scope.enteredsystem_scope.completedsystem_scope.failed
失败操作也必须保留审计。日志用于可观测性,数据库审计用于业务追溯,二者不能互相替代。
7.3 调用边界
- Controller 不得直接注入
ITenantExecutionScope。 - 普通 tenant service 不得取得全局 System Scope。
- 仅平台管理、Worker、迁移、公共题库和明确审核的基础设施服务可以使用。
- 架构测试维护允许调用方白名单。
- 有明确目标租户的操作必须提供
TargetTenantId;只有真正的全局任务可以为空。
7.4 自动化验收
- 普通请求 scope 不能切换 tenant。
- System Scope 必须创建新的 DI scope。
- System Scope 仍不能修改已有实体的 TenantId。
- 成功和失败操作都有持久化审计。
- 平台 actor、目标租户、job、reason 和 trace 信息完整。
- 未在白名单中的服务引用
ITenantExecutionScope时架构测试失败。
7.5 建议提交
feat(audit): persist privileged tenant scope operations
8. P2:Token、隐私与协议规范
8.1 客户端认证策略
按客户端明确契约:
- App、小程序:可以继续使用 JSON access/refresh token。
- 浏览器后台:优先评估 BFF 或
HttpOnly + Secure + SameSiteCookie。 - 若浏览器继续使用 Bearer token,必须明确 refresh token 存储、CSP、XSS 防护和清理策略。
- Cookie 与 Bearer 并存时必须配置清晰的 authentication scheme 选择规则。
8.2 Token 最小化
Access token 原则上只保留:
sub sid jti iat iss aud exp scope tid amr
手机号和邮箱不是授权必需 claim,客户端可以通过 /api/me 获取,避免 token 泄漏扩大 PII 暴露。
8.3 登录审计隐私
- 手机号和邮箱采用脱敏展示。
- 使用服务端 HMAC 摘要支持稳定关联分析,不直接依赖完整 identifier。
- 定义登录事件、IP、User-Agent 的保留期限和清理任务。
- 限制查询和导出审计日志的权限。
- 审计导出和清理本身也要记录审计。
8.4 OIDC 演进条件
短期可以保留当前第一方 JWT/Session 体系。出现以下任一需求时,应采用标准 Identity Provider 或 OpenIddict 等 OIDC/OAuth 方案,而不是继续扩展自定义协议:
- 第三方应用接入
- 企业 SSO
- Authorization Code + PKCE
- 标准 discovery/JWKS
- 多 API audience
- 联邦身份
- service-to-service client credentials
9. 测试与发布门槛
完成补强后,生产发布必须满足:
dotnet test TIKU-BACKEND.slnx全量通过。- PostgreSQL 特有行为由真实 PostgreSQL 集成测试证明,不能使用 EF InMemory 替代。
- Production 缺少 JWT key、Data Protection 证书、短信 pepper、Platform Host 或可信代理时启动失败。
- 所有 Controller action 都进入 endpoint authorization manifest。
- 每个后台模块至少覆盖无权限、无套餐、跨租户、越 DataScope 和无 MFA 的负向测试。
- Disabled 用户、成员、租户、角色和 permission 撤销能立即使旧 Session 失效。
- Forwarded Host 伪造测试通过。
- Refresh token 并发、轮换和重放撤销测试继续通过。
- System Scope 全部调用点通过架构白名单,并产生持久化审计。
- 迁移 SQL 已人工检查 tenant-qualified foreign key、唯一约束、check constraint 和新增审计结构。
- 执行
dotnet format TIKU-BACKEND.slnx --verify-no-changes --no-restore。 - 执行
git diff --check。
10. 阶段验收与版本划分
安全基线版本
完成:
- P0 可信代理与 Host 边界
- P0 Disabled 成员生命周期
该版本解决已识别的直接安全边界和状态恢复风险。
SaaS 授权完整版本
完成:
- P1 Capability / 套餐授权
- P1 endpoint manifest 与 DataScope 审计
- P1 System Scope 持久化审计
该版本形成套餐、模块、操作权限、数据范围和特权操作的完整 SaaS 授权闭环。
客户端与合规版本
完成:
- P2 浏览器认证策略
- Token claim 最小化
- PII 保留与脱敏
- OIDC 演进决策
该版本用于正式客户端规模化、第三方接入和后续合规治理。
11. 明确不在本计划内的事项
- 不引入 PostgreSQL RLS;继续使用请求租户上下文、EF Query Filter、写入拦截器、组合约束和真实 PostgreSQL 测试。
- 不把 tenant/platform permission 放入 JWT 作为直接授权事实。
- 不允许前端菜单或 feature flag 成为唯一授权依据。
- 不因为补强认证体系而重新引入 Supabase 运行时依赖。
- 不在没有第三方接入需求时立即重写为完整 OIDC Provider。