Skip to content
bitzorcas
中EN

Concept

Multitenancy 多租户隔离

从租户解析、可信上下文、状态守卫、ORM 过滤、后台作业到运维切换,系统理解多租户隔离链和当前交付边界。

Last updated

多租户不是“每张表多一个 TenantId”。它是一条从请求证据到数据访问的连续信任链:边界选择有效租户,状态守卫判断是否可服务,上下文把同一个快照传播给 ORM、缓存和审计,后台作业和运维工具则必须显式建立可信作用域。

1. 能力分布在框架、Host 和 Identity

源码区域主要类型责任
DomainTenantId、TenancyDefaults、ITenantEntity、TenantAggregateRoot稳定标识、默认值和租户模型契约
ApplicationTenantResolver、八个 ResolutionStep、ITenantGuard传输中立的解析和可服务状态协议
Application AbstractionsCurrentTenant、ICurrentTenant、ICurrentTenantAccessor不可变快照、只读消费和 AsyncLocal 作用域
API Host两个租户 Middleware、HttpContextCurrentTenantHTTP 输入映射、作用域安装和会话校验
Infrastructure持久化执行上下文、EF / SqlSugar QueryFilter把有效租户落实到查询和审计
IdentityPlatformTenant、TenantStatusGuard、Host 映射、模拟 Grant/Token 服务租户状态、域名事实和正式运维授权

Multitenancy 是跨层基础能力,不是一个独立 src/Platform/Multitenancy 项目。只阅读模块页或只搜索 Platform 会漏掉真正的安全实现。

2. HTTP 主路径

UseAuthentication

DelegationTokenMiddleware

TenantResolutionMiddleware

八步 Resolver
首个有效结果

CurrentTenant AsyncLocal

TenantStatusGuard

TenantImpersonationTokenMiddleware

QueryFilter / 缓存 / 审计

UseAuthorization + Endpoint

租户解析位于认证之后,因此能使用可信 CurrentUser;租户模拟会话校验位于解析之后,因此读取 ICurrentTenant;授权与业务 Endpoint 位于它们之后。

3. 八步解析链

DI 注册顺序就是优先级:

  1. SystemJobTenantResolutionStep:显式系统作业字段或已有 Ambient Scope;
  2. RootOperatorOverrideTenantStep:Host 操作员覆盖或开发调试覆盖;
  3. ApplicationCallerTenantStep:已认证 Application 的 TenantId;
  4. UserClaimTenantStep:已认证 User 的 TenantId;
  5. HostSubdomainTenantStep:完整 Host 到平台租户映射;
  6. HeaderTenantStep:X-Tenant 只在等于已认证租户时确认;
  7. PathTenantStep:路径首段只在等于已认证租户时确认;
  8. SingleTenantDefaultTenantStep:固定回退 1000001。

Resolver 取第一个满足 TenancyDefaults.IsValid 的字符串。当前所谓“有效”仅表示非空且不等于 "0";TenantId.Of 也不会验证雪花格式、租户存在性或状态。这些由后续 Guard 承担。

详见 租户解析链。

4. 只允许一个租户事实轨道

ICurrentTenantAccessor 使用 AsyncLocal<CurrentTenant>,BeginScope 可嵌套并在 Dispose 时恢复外层。ICurrentTenant 是业务只读入口;旧 ITenantContext 只是同一 Accessor 的兼容投影,不保存第二份字段。

后台处理器进入可信租户作用域
var tenantId = await ownership.ResolveTenantAsync(message.AggregateId, cancellationToken);
if (!TenancyDefaults.IsValid(tenantId))
{
throw new InvalidOperationException("The message has no trusted tenant owner.");
}
// BeginScope 同步推入 CurrentTenant 和持久化执行上下文;Dispose 恢复外层。
using var tenantScope = tenantAccessor.BeginScope(new CurrentTenant(tenantId));
// 从这一行开始,Repository、缓存键和审计都读取同一个有效租户。
await handler.ProcessAsync(message, cancellationToken);

BeginScope 不做 I/O、授权或状态验证。调用方必须先从队列父记录、Grant 或其他可信 owner 解析租户。

5. 数据隔离的两层保证

Tenant 模型实现 ITenantEntity。生成式持久化元数据为 EF Core 组合 Tenant 与软删除全局过滤器;SqlSugar 在 Scope 配置中注册 ITenantEntity Filter。显式忽略软删除时,两个 Repository 仍会重新加回租户谓词。

| Provider | 默认过滤 | 显式包含软删除时 | | -------- | ------------------------------------------------------- | ------------------------------ | ------------------------------ | --------------------------------------------- | | EF Core | IsTenantFilterBypass | | TenantId == TenantFilterValue | IgnoreQueryFilters() 后手工恢复 Tenant 谓词 | | SqlSugar | ITenantEntity.TenantId == resolvedTenant + SoftDelete | 清过滤器后手工恢复 Tenant 谓词 |

业务查询仍应显式表达 owner、Office、部门和 Authorization DataScope;全局 Tenant Filter 只解决租户隔离。

详见 CurrentTenant 与持久化隔离。

6. Host 覆盖与正式模拟不是同一能力

X-Operate-As-Tenant 只对已认证 CallerType.Host 生效;X-Tenant-Debug 只在 Development 生效。它们当前都写入普通 CurrentTenant,并跳过 TenantStatusGuard。

正式租户模拟模型则要求 TenantImpersonationGrant、最长两小时 Token、操作人租户、GrantId 和独立到期时间,TenantImpersonationTokenMiddleware 会检查过期与主动撤销。

更重要的是,EF Core 与 SqlSugar 当前都让所有已认证 Host 调用者绕过 Tenant Filter。即使 X-Operate-As-Tenant 把 EffectiveTenantId 改为目标租户,Host bypass 仍为真,持久化查询并不会自动收窄到目标租户。这是 GA 前必须修复并做双 ORM 集成测试的安全边界。

详见 运维租户切换与模拟。

7. 状态守卫

生产 TenantStatusGuard 从 ITenantStore 读取租户,只允许 Active 与 GracePeriod。未知、Suspended、Expired、PendingProvisioning 和 Deactivated 均拒绝;默认内存 Store 仅对未注册的 1000001 保留单租户兼容放行。CallerType.System 直接绕过 Guard。

HTTP Middleware 还会对 RootOverride 和 Debug 路径跳过 Guard。因此“所有解析结果都经过状态校验”不是当前事实。

8. 后台任务、缓存和审计

  • Job/CAP Consumer 必须从消息 owner 或父记录解析租户,再开 BeginScope;
  • WorkflowTimerJobExecutionScope 会从父流程实例解析租户,并专门替换 SqlSugar 的 Tenant Filter;
  • 缓存键、文件 Object Key、搜索、报表和审计都要包含 EffectiveTenantId;
  • 正式模拟时数据范围使用 EffectiveTenantId,审计还应保留 ActorTenantId、ImpersonatorUserId 与 GrantId;
  • 系统队列可以跨租户扫描,但每一条业务处理必须回到单租户作用域。

详见 后台任务与租户化资源。

9. 章节地图

目标页面
理解优先级、Host 映射、Header/Path 防伪与默认租户租户解析链
使用 CurrentTenant 并核验双 ORM QueryFilter上下文与持久化
设计 Host 运维、Grant、Token 和审计边界租户切换与模拟
编写 Job、Consumer、缓存、文件和搜索适配后台任务与租户化资源
建立隔离测试、排障和故障演练测试与生产运维

10. 源码审查入口

Terminal window
# 同时审查解析、Host 中间件、CurrentTenant 和两个 ORM,不能只看 Application。
rg -n "TenantResolver|TenantResolutionMiddleware|CurrentTenant|TenantFilter" \
src/Framework src/Hosts/BitzOrcas.Api src/Platform/Identity -g '*.cs'
# 当前预期能看到 Host bypass 只依赖 CallerType.Host;修复后必须重审本手册。
rg -n "IsTenantFilterBypass|isHostBypass|CallerType.Host" \
src/Framework/BitzOrcas.Infrastructure.EfCore src/Framework/BitzOrcas.Infrastructure.SqlSugar -g '*.cs'
# 搜索正式模拟会话的 HTTP 闭环;当前没有签发 Endpoint 或 Claim 还原实现。
rg -n "TenantImpersonationTokenDescriptor|TenantImpersonationGrantId" \
src/Hosts src/Platform/Identity -g '*.cs'

11. GA 最低证据

  1. 所有租户解析来源都有明确可信级别和冲突语义;
  2. Host operate-as 后两个 ORM 都只返回目标租户,而不是全局绕过;
  3. Override、Debug 和正式模拟目标均检查租户存在与可服务状态;
  4. 正式模拟签发、Claim、CurrentTenant 还原、到期、撤销和审计完整闭环;
  5. Job、Consumer、缓存、文件、搜索与报表均有 A/B 租户隔离测试;
  6. IgnoreQueryFilters、QueryFilter.Clear 与系统级跨租户操作均被架构或契约测试审查。

下一篇:租户解析链 · 返回平台模块目录

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%