147 lines
7.5 KiB
Markdown
147 lines
7.5 KiB
Markdown
# 认证、授权与 Host 安全策略
|
||
|
||
本文是当前生效安全规范。新增接口或修改登录流程时,以本文档和自动化测试为准;前端菜单、JWT 字符串和历史角色约定不能代替 API 授权。
|
||
|
||
## 授权域
|
||
|
||
- `tenant`:租户业务域,必须绑定 Active 租户和 Active `TenantMembership`。
|
||
- `platform`:平台运营域,只能从配置的 Platform Host 进入,不绑定租户。
|
||
|
||
核心规则:
|
||
|
||
- Host、JWT scope、tenant claim、数据库 Session 和请求租户上下文必须一致。
|
||
- JWT 只证明已认证会话,不承载可直接授权的角色或权限。
|
||
- 后台权限每次从数据库角色绑定解析;菜单只控制 UI 展示。
|
||
- 租户后台能力同时要求 Active tenant、有效订阅、模块权益和 operation permission;Capability 仍以 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 family;logout-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 Outbox,Worker 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)。
|