# 认证、授权与租户隔离

本文说明当前请求实际经过的安全边界。新增接口、实体或后台任务时，必须保持这些边界闭合。

## 认证入口

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 撤销当前 Session，logout-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
Realm（platform / 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 和额度使用量来自 PostgreSQL；Redis 只用于失效通知和缓存，不能成为授权真相。

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 与后台处理

跨租户 Hosted Service、迁移、seed 和平台级后台操作必须通过 `ITenantContextInitializer.InitializeSystem` 或受审计的 `ITenantExecutionScope` 进入 System Scope，并提供明确原因。业务代码不得直接关闭 Query Filter。

- Session、成员、租户和套餐状态始终从 PostgreSQL 重新校验。
- 租户、套餐和 Feature 变更在数据库提交后直接失效当前 API 进程与 Redis 中的相关缓存。
- 后台任务在业务事务提交后持久化到 PostgreSQL，Hosted Service 使用租约执行；延时和重试由 `RunAfter` 控制。

## 安全配置门禁

Production 还会在启动时验证 Redis、Data Protection 证书、租户 Secret master key、短信 pepper、CORS 和外部服务配置。完整配置入口见[配置与后台任务](../operations.md)。
