From b8d14e8a7ee7a70b28e541e9e70fac2a409d449e Mon Sep 17 00:00:00 2001 From: xiong Date: Tue, 28 Jul 2026 15:59:56 +0800 Subject: [PATCH] docs: add authentication hardening plan --- ...entication-authorization-hardening-plan.md | 415 ++++++++++++++++++ 1 file changed, 415 insertions(+) create mode 100644 docs/architecture/authentication-authorization-hardening-plan.md diff --git a/docs/architecture/authentication-authorization-hardening-plan.md b/docs/architecture/authentication-authorization-hardening-plan.md new file mode 100644 index 0000000..6b69640 --- /dev/null +++ b/docs/architecture/authentication-authorization-hardening-plan.md @@ -0,0 +1,415 @@ +# 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. 目标与原则 + +补强后的每次授权应形成以下闭环: + +```text +User Active + + Session Active + + Host / Realm / Tenant 一致 + + Tenant Active + + Membership Active + + Subscription 有效 + + 套餐包含目标模块 + + Operation Permission + + DataScope / Resource Scope + + 敏感操作 MFA +``` + +实施遵循以下原则: + +1. 先处理可导致安全边界绕过或权限生命周期失效的问题,再扩展产品能力。 +2. 所有安全配置默认 fail-closed,不能只依赖部署文档提醒。 +3. JWT 继续只表达已认证会话,不承载可直接授权的角色和权限。 +4. 前端菜单、feature flag 和页面隐藏永远不能代替 API 授权。 +5. 列表、详情、写入、批量、导出和异步任务使用一致的数据范围。 +6. 每个阶段独立实现、独立测试、独立提交;未通过本阶段验收不得进入下一阶段。 + +## 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 天 | 正式客户端规模化前完成 | + +推荐交付顺序: + +```text +可信代理 + -> 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 实施范围 + +1. 为 `TenantResolutionOptions` 增加生产配置校验: + - Production 必须配置至少一个正式 `PlatformHosts`。 + - Production 必须配置至少一个合法 `TrustedProxyAddresses`。 + - Production 不得只保留 `localhost` 或 `127.0.0.1` 作为 Platform Host。 + - 代理地址无效时应用启动失败。 +2. 调整 `ForwardedHeadersOptions`: + - 只有存在合法可信代理配置时,才替换框架默认 proxy/network 限制。 + - 保持 `ForwardLimit = 1`。 + - 显式配置 `AllowedHosts`。 + - 只处理部署所需的 `X-Forwarded-For`、`X-Forwarded-Host` 和 `X-Forwarded-Proto`。 +3. 收紧 Host 配置: + - 生产 `AllowedHosts` 不允许使用 `*`。 + - Platform Host、租户公共主域名和允许的网关 Host 必须形成可审计配置。 +4. 固化网关契约: + - 网关覆盖客户端提供的 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 建议提交 + +```text +fix(security): fail closed on forwarded host trust +``` + +## 4. P0:修正 Disabled 成员生命周期 + +### 4.1 固定状态语义 + +| Membership 状态 | 登录行为 | 状态改变方式 | +| --- | --- | --- | +| Active | 允许继续认证 | 正常业务流程 | +| Invited | 不得静默激活 | 显式接受邀请或管理员确认 | +| Disabled | 所有登录方式拒绝 | 仅管理员显式恢复 | +| 不存在 | 根据租户自注册策略决定 | 创建新 Student 或拒绝 | + +### 4.2 实施范围 + +1. 外部身份登录只能创建从未存在过的 Student membership,不能恢复历史 membership。 +2. 查询到 Disabled membership 时返回统一租户访问拒绝,不能改回 Active。 +3. 查询到 Invited membership 时进入显式邀请接受流程,不能由微信登录静默激活。 +4. 建议增加租户级注册策略: + - `AllowStudentSelfRegistration` + - `AllowedSelfRegistrationProviders` + - `RequireRegistrationApproval` +5. 成员恢复必须复用管理员权限、DataScope 和审计,不允许隐藏在登录流程里。 + +### 4.3 自动化验收 + +- Disabled Student 不能通过微信 Web 或小程序登录恢复状态。 +- Disabled Student 的旧 access token 和 refresh token 继续立即失效。 +- Invited 成员不会被外部身份登录静默转为 Active。 +- 允许自注册时,首次微信登录可以创建 Student membership。 +- 关闭自注册时,首次微信登录被拒绝。 +- 管理员显式恢复并写入审计后,成员才能重新登录。 + +### 4.4 建议提交 + +```text +fix(auth): preserve disabled tenant membership state +``` + +## 5. P1:建立统一 SaaS Capability 授权层 + +### 5.1 目标 + +现有 RBAC 继续负责“用户是否有操作权限”,新增 Capability 层负责“租户当前是否购买、启用并可使用该能力”。两者必须同时成功。 + +### 5.2 Application 抽象 + +建议在 Application 层新增只读上下文: + +```csharp +public interface ICurrentTenantCapabilityContext +{ + Task GetAsync( + CancellationToken cancellationToken = default); +} +``` + +Snapshot 至少表达: + +- `TenantId` +- `SubscriptionStatus` +- `SubscriptionExpiresAt` +- `EnabledModules` +- `FeatureFlags` +- `UsageLimits` +- `IsReadOnly` +- `DenialReason` + +建议增加以下 Authorization Requirement: + +- `TenantSubscriptionRequirement` +- `TenantModuleRequirement(moduleCode)` +- `TenantUsageRequirement(resourceCode)` + +### 5.3 Policy 组合 + +Controller 不得分别手工检查套餐和权限。现有 permission policy 应由统一注册器或自定义 policy provider 组合: + +```text +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 建议提交 + +```text +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 字符串,至少包含: + +- `Operation` +- `TargetTenantId` +- `ActorUserId` 或 Worker identity +- `JobId` +- `CorrelationId` +- `Reason` +- `RequestedAt` + +### 7.2 持久化审计 + +System Scope 进入和退出都写数据库: + +- `system_scope.entered` +- `system_scope.completed` +- `system_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 建议提交 + +```text +feat(audit): persist privileged tenant scope operations +``` + +## 8. P2:Token、隐私与协议规范 + +### 8.1 客户端认证策略 + +按客户端明确契约: + +- App、小程序:可以继续使用 JSON access/refresh token。 +- 浏览器后台:优先评估 BFF 或 `HttpOnly + Secure + SameSite` Cookie。 +- 若浏览器继续使用 Bearer token,必须明确 refresh token 存储、CSP、XSS 防护和清理策略。 +- Cookie 与 Bearer 并存时必须配置清晰的 authentication scheme 选择规则。 + +### 8.2 Token 最小化 + +Access token 原则上只保留: + +```text +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. 测试与发布门槛 + +完成补强后,生产发布必须满足: + +1. `dotnet test TIKU-BACKEND.slnx` 全量通过。 +2. PostgreSQL 特有行为由真实 PostgreSQL 集成测试证明,不能使用 EF InMemory 替代。 +3. Production 缺少 JWT key、Data Protection 证书、短信 pepper、Platform Host 或可信代理时启动失败。 +4. 所有 Controller action 都进入 endpoint authorization manifest。 +5. 每个后台模块至少覆盖无权限、无套餐、跨租户、越 DataScope 和无 MFA 的负向测试。 +6. Disabled 用户、成员、租户、角色和 permission 撤销能立即使旧 Session 失效。 +7. Forwarded Host 伪造测试通过。 +8. Refresh token 并发、轮换和重放撤销测试继续通过。 +9. System Scope 全部调用点通过架构白名单,并产生持久化审计。 +10. 迁移 SQL 已人工检查 tenant-qualified foreign key、唯一约束、check constraint 和新增审计结构。 +11. 执行 `dotnet format TIKU-BACKEND.slnx --verify-no-changes --no-restore`。 +12. 执行 `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。