Files
tiku-backend.net/docs/architecture/security-and-tenancy.md

8.9 KiB
Raw Blame History

认证、授权与租户隔离

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

认证入口

API 支持两组认证接口:

  • /api/tenant/auth/** 返回 access token 与 refresh token适合 Bearer 客户端。
  • /api/tenant/auth/browser/** 把 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 撤销当前 Sessionlogout-all 撤销用户全部 Session。
  • 平台 token 只能在平台 Host 使用;租户 token 必须与 Host 或允许路径上的 tenant code 解析结果一致。上下文冲突返回 401不允许静默切换租户。

Production 必须显式配置 JWT KeyId、私钥和验证公钥集合,不能使用 Development 临时密钥。

Browser Auth 使用 access、refresh 和 CSRF Cookie

  • access/refresh Cookie 为 HttpOnly。
  • Bearer handler 只会在同源浏览器请求中回退读取 access Cookie显式 Authorization header 优先。
  • 使用 Cookie 的非安全方法必须通过 BrowserCsrfMiddleware 的 Origin/Referer 与 CSRF token 校验。
  • 跨源浏览器使用必须同时正确配置 CorsBrowserAuth:AllowedOrigins;允许凭据时不能使用通配 Origin。

非浏览器客户端应使用 Bearer token不应复制浏览器 Cookie 流程。

Realm、Permission、Feature 与 DataScope

每个受保护操作可能同时经过四层判断:

Realmplatform / tenant
  + BackendPermission操作权限
  + SaaSFeature套餐能力
  + DataScope资源范围
  • Realm 防止平台身份、租户员工和学生身份跨授权域复用。
  • BackendPermission 控制 view/manage/read/write/operate 等操作。
  • SaaSFeature 是固定代码目录当前包括后台基础、私有题库、练习、作业、考试、词汇、手册、视频、分数线、站点内容、学生管理、学生商城、CRM、推广分佣和教师 AI。
  • 菜单由有效 Permission 与 Feature 共同推导,只用于 UI bootstrap不是 API 授权依据。
  • DataScope 支持 AllRestrictedSelf。无法提供可靠资源 predicate 时返回空查询,不能退化为“当前租户全部数据”。
  • 套餐状态、Feature override 和额度使用量来自 PostgreSQLRedis 只用于失效通知和缓存,不能成为授权真相。

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-codetenantCode 解析租户。
  4. JWT tenant ID 与已解析租户必须一致,否则认证失败。

Development 默认平台 Host 是 localhost127.0.0.1。Production 启动校验要求:

  • 至少一个非 loopback 的正式平台 Host
  • 非通配 AllowedHosts
  • 至少一个合法的 TrustedProxyAddresses
  • 仅信任一跳且来源位于可信代理列表的 X-Forwarded-For/Host/Proto

客户端不得通过任意 header、query 或转发头绕过以上路径和可信代理限制。

主域名与 Owner 一次性激活

新租户创建时必须提供主域名。域名统一转换为小写 ASCII/IDN Host并使用随机 32 字节 Base64Url TXT 值验证所有权;只有 DNS 与 TLS 都成功后才进入 Active。平台领取 Owner 激活链接前还会校验租户、Active 主域名和试用/订阅状态。

Production 默认激活链接为 https://{primaryHost}/activate/{activationId}#token={token}Development 可通过专用 localhost 模板指向租户前端端口。Token 位于 fragment不进入 HTTP 请求、服务器访问日志或 RefererPostgreSQL 只保存 SHA-256 哈希。Grant 绑定签发时的 DomainId,浏览器激活要求请求 Host、已解析 Tenant、Grant 和 Active 主域名完全一致。并发签发由事务 advisory lock 和部分唯一索引收敛为一个有效 Grant幂等重放只返回 Grant ID 与过期时间,不能恢复明文。

浏览器激活在受审计事务中消费 Grant、设置密码、清除强制改密状态并创建数据库 Session。成功响应仅写入 HttpOnly access/refresh Cookie 与可读 CSRF Cookie不向 JavaScript 返回 Token Pair密码策略失败会整体回滚Grant 不会提前消费。

数据库租户隔离

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

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

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

Redis 授权缓存模式

Security:AuthorizationCache:Mode 支持三种当前实现模式:

  • Disabled:直接以 PostgreSQL 完成安全状态和权限读取。
  • ShadowPostgreSQL 决策仍为准,同时读取、回填并比较 Redis 结果。
  • Active:优先读取 Redis 安全状态与权限快照;缓存缺失或 Redis 不可用时回退 PostgreSQL。

缓存键按环境、realm、租户、用户和 Session 隔离,不保存 JWT、Refresh Token、手机号或邮箱。权限快照键包含持久化授权版本用户、成员、租户、Session 或角色权限变化会推进版本并触发缓存失效。提交后若 Redis 失效失败,事件保留在 PostgreSQLAuthorizationCacheInvalidationWorker 重试;旧版本不会覆盖新版本。

Redis 不是授权事实源。Redis 与 PostgreSQL 同时无法完成安全校验时返回 503 auth_security_unavailable,不能使用本地旧快照放行。

安全配置门禁

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