docs: add authentication hardening plan
This commit is contained in:
415
docs/architecture/authentication-authorization-hardening-plan.md
Normal file
415
docs/architecture/authentication-authorization-hardening-plan.md
Normal file
@@ -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<CurrentTenantCapabilitySnapshot> 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。
|
||||
Reference in New Issue
Block a user