BitzOrcas 提供两条受控支持路径:用户模拟登录代表同租户用户处理业务;Tenant Impersonation 让平台操作员在已有授权下切入客户租户。两者都会保留真实操作者,但改变的安全上下文不同,不能混成一个“切换身份”按钮。
两条安全链路
普通用户身份、Delegated caller 和 Host operator 是不同调用方类型。业务代码应读取 ICurrentUser 与租户上下文,不从任意请求头或散落 Claim 自行推断代理状态。
场景与不变量
| 场景 | 改变什么 | 必须保留什么 |
|---|---|---|
| 用户模拟登录 | 当前业务用户 | target、impersonator、租户、原因、会话代际、期限与 handoff 模式 |
| Tenant Impersonation | 当前租户 | operator、原租户、目标租户、grant |
签发服务拒绝嵌套代理,避免出现“甲代理乙后再代理丙”的不可审计链。用户模拟登录要求精确权限、同租户且非 Host 的目标、原因和 1–120 分钟时长;不存在记录时会创建 DelegationGrant,并不要求目标用户预先批准。Tenant Impersonation 则拒绝空目标和切入自身租户,并要求存在处于有效期内的跨租户 grant。
两类短期令牌的硬上限都是两小时。用户模拟登录按请求的 1–120 分钟签发;Tenant Impersonation 的期限还受已有 grant 剩余时间约束。
两类 grant 的建立方式不同
两条链路都把服务端 grant 视为可撤销的权威事实,把 token 视为短期投影;差别在于 grant 如何建立。
- 用户模拟登录由具备
identity.user.impersonate的管理员直接发起。服务在同一“管理员 + 目标用户”记录上创建或轮换会话代际,保存 Reason、SessionId、ExpiresAt 与 HandoffMode。 - Tenant Impersonation 面向跨租户 Host 支持,必须先有覆盖 operator 与目标租户的有效授权记录。
受控发起/跨租户审批 → 服务端 grant + 会话代际 → 短期 token → 每请求复核 grant │ └─ revoke/reissue/expire 立即失败调用 JWT builder 不能绕过对应 Application Handler、精确权限、目标归属和权威记录。紧急 break-glass 仍应建立独立、高强度、短时且强审计的授权路径。
请求管线位置
API 先建立基本认证,再运行 Delegation middleware,随后解析 tenant guard,并在之后运行 Tenant Impersonation middleware。两种 middleware 都会检查令牌独立过期和授权现状。
Authentication → DelegationTokenMiddleware → TenantResolution / TenantGuard → TenantImpersonationTokenMiddleware → Audit / HTTP Authorization / EndpointsDelegation middleware 当前把声明不完整或独立期限已到映射为 Delegation.Expired,把 grant 不存在、撤销、重签代际变化或权威查询失败映射为 Delegation.Revoked,均返回 401。Delegation.Unavailable 仍在 Host 错误目录中,但当前中间件路径不使用它。失败后不得以原调用者“静默降级”。
Delegation 的真实边界
Delegated token 把有效 caller 投影为 CallerType.Delegated,同时保留 impersonator、grant、session 与独立 expiry。授权决策缓存对 Delegated 调用者直接绕过,因为会话有自己的撤销和代际节奏;复用普通用户缓存可能让撤销失效。
// 业务用例只读取统一上下文,不直接解析 delegation claim。var actor = currentUser.UserId;var realOperator = currentUser.ImpersonatorId;
// 审计必须同时保留业务主体与真实操作者。audit.Record(actorId: actor, impersonatorId: realOperator);DelegationGrantStore 已改为消费 IEntitySet<DelegationGrant> 与统一 ICommandRepository,共享集成测试同时登记 SqlSugar 和 EF Core 泛型仓储。交付验证仍需覆盖两套 Provider 的唯一键、代际轮换、HandoffMode 列与事务语义;不能只凭 ImpersonatorId 字段存在就宣称完整 parity。
浏览器 handoff 支持 TabScoped 与 SiteWide。前者只让目标标签页采用短期 access;后者通过同源广播同步已打开标签页,并以不含 Token 的 SessionId 指针配合管理员 HttpOnly cookie 完成冷启动 resume。未配置租户策略时默认 TabScoped,请求可以显式覆盖。
Tenant Impersonation 的真实边界
Tenant Impersonation 用于平台支持和运维,而非客户侧角色切换。它保留 operator user id 和原 tenant,并把下游 tenant context 切换到目标租户。任何查询仍必须经过目标租户过滤与资源授权。
// 签发时拒绝空目标、自身租户、嵌套状态和无有效 grant。var descriptor = await tokenService.IssueAsync( @operator, targetTenantId, cancellationToken);
// 成功描述符的期限不会超过 grant 剩余时间或两小时上限。return descriptor.ExpiresAt;切入目标租户不自动授予该租户管理员权限。授权层仍要根据专用支持权限、动作限制与资源属性决策;Feature entitlement 与 DataScope 也不能被跳过。
高风险动作
代理期间建议禁止或追加 step-up 的操作包括:修改认证因子、创建/导出密钥、变更角色权限、删除租户、发起付款、批量导出 PII 和更改审计保留策略。
限制应落实在授权策略或 Handler precondition,而不只是在 UI 隐藏按钮。API 客户端、后台命令和旧前端都可能绕过展示层。
public static class DelegationErrors{ public static readonly Error ForbiddenAction = Error.Forbidden( "Security.Delegation.ForbiddenAction", "This action cannot run in a delegated session.");}
// 对不可代理动作明确拒绝,避免依赖前端隐藏。if (currentUser.CallerType == CallerType.Delegated) return Result.Failure(DelegationErrors.ForbiddenAction);用户体验
进入代理状态前显示目标身份、租户、理由、到期时间与 handoff 模式;进入后使用持续可见的横幅,并提供立即退出。TabScoped 要明确哪些标签页仍是管理员,SiteWide 要同步采用与退出,并在失败时撤销服务端会话。
退出不能只清除本地 token。/exit 撤销当前 JWT 的精确代际,真实管理员 DELETE 可幂等撤销目标会话,/restore 再用 HttpOnly refresh cookie 恢复管理员身份。客户可见的支持记录应遵守隐私与安全披露策略。
审计证据
每次签发、使用、拒绝、退出、过期和撤销都应关联:
- 原始操作员、业务主体、原租户与目标租户;
- grant/token 标识、理由、工单与审批人;
- action/resource、结果、时间和客户端上下文;
- CorrelationId、TraceId 与受影响聚合;
- 高风险动作的额外审批或拒绝原因。
日志和审计不能保存代理 token 明文。查询界面应能按操作员、客户租户、时间段和工单重建完整会话。
测试矩阵
- 用户模拟登录覆盖无旧记录自动创建、重签代际、空目标、自己、Host 与跨租户拒绝;
- 时长只接受 1–120 分钟;Tenant Impersonation 仍受跨租户 grant 剩余期限约束;
- Delegation 与 Tenant Impersonation 均拒绝嵌套;
- 撤销后下一请求失败,且不回退原身份继续执行;
- 授权缓存不会延长 delegated session;
- 目标租户查询仍受 tenant filter 与 DataScope 限制;
- 审计同时保存 actor 与 impersonator;
- TabScoped 不可 resume,SiteWide 的 cookie/Origin/平台/SessionId 均需校验;
- 各 persistence profile 的支持/拒绝行为都有 Consumer Contract Test。
当前差距与采用建议
当前主线已具备两类 token service、middleware、用户模拟登录发起/当前状态/退出/撤销/恢复端点、双 handoff 模式、租户策略、敏感命令统一拒绝和审计承载。Provider 采用仍不能只验证登录后 Claim;必须把 grant 保存、代际轮换、逐请求撤销、handoff schema 迁移和审计查询纳入合同测试。
前端启用双模式前必须先应用 202608090007-impersonation-handoff-mode.sql。当前源码工作树还为 SqlSugar CodeFirst 非空列补了 DEFAULT 'TabScoped';发布时应把这项未提交改动与文档所依据的源码修订一起纳入评审。