大型后台管理系统如何把业务操作做可靠(7):多租户授权、撤权与审计
客服同时属于甲、乙两家商户。在甲可以发起退款,在乙只有查看权限。后台页面缓存了一份甲的操作列表,此时管理员撤销其权限,队列中还有一笔已经受理的退款。页面、接口、执行进程和下载入口各自要知道该按谁的身份、哪个租户和哪一时刻的授权来处理。
先写清授权决策的输入
一次判断至少需要主体、租户、动作和资源。主体来自经过验证的会话或任务授权记录,租户是主体当前有资格访问的组织,动作是退款、读取或导出等业务能力,资源是当前租户下的订单或文件。角色可以授予退款能力,资源条件还要约束负责的门店和金额上限。金额是否可退则是业务检查,可以返回不同的业务原因。
下面的伪代码展示一个直接写入的路径。列表查询要把同样的数据范围约束下推到查询中,否则分页数量、汇总和导出都可能越过可见范围。
refundOrder(session, selectedTenant, orderId, input):
actor = authenticate(session)
membership = requireActiveMembership(actor.id, selectedTenant)
order = loadOrderWithinTenant(membership.tenantId, orderId)
requirePolicyAllows(actor, membership, "refund", order, input.amount)
return executeRefundCommand(actor, membership.tenantId, order, input)
客户端传入租户选择可以帮助路由,但服务端要验证成员关系,之后在数据库、缓存、搜索、对象存储和任务队列中沿用这个租户上下文。缓存按租户和权限范围隔离。前端切换租户时使旧请求、缓存、订阅和草稿失效,或在隔离的命名空间保留草稿。多租户任务还可以按租户设并发和资源配额,避免一个租户的重型导出挤满所有执行资源。AWS 跨租户资源隔离指导 · AWS 对共享资源争抢的说明
区分受理授权与执行授权
有些操作一旦审批通过就由系统完成,例如企业已批准的定时结算。这时保存明确的审批和委托依据。另一些操作需要提交者仍具备权限,例如等待中的人工退款。任务记录应保存发起主体、租户、目标、授权策略及必要的审批引用,由执行端使用当前权限重新判断。执行端本身拥有访问资源的服务身份,还需以任务归属约束实际允许处理的数据。
撤权时刻需要和业务方约定。若产品承诺撤权立即阻止新的写入,那么只靠带过期时间的权限缓存无法兑现。可以在提交边界核对新鲜权限或同一事务内的授权版本。外部动作通常无法与本地权限更新共用事务,应明确已经进入不可逆执行阶段的操作如何记录和收尾。运行数分钟的批量任务可以在批次边界重新检查,剩余记录按约定停止。Zanzibar 的一致性设计展示了为什么授权与资源更新的先后顺序值得明确。Google Zanzibar 论文
下载和审计也有各自的入口
导出生成时按允许的数据范围取数,文件记录租户、创建者和数据范围。下载时如果权限缩小到不足以读取整份文件,应拒绝交付或重新生成符合新范围的文件。直接签发的下载链接会在其有效期内独立工作,需要实时撤权的场景应通过可再次鉴权的入口交付,或使用明确可撤销的机制。
审计事件可以从已提交的业务事务或可靠的待投递记录中产生。一次退款保存操作 ID、真实操作者、代操作身份、租户、订单、审批引用、动作结果和发生时间。日志按需记录错误类别和关联 ID,敏感表单、访问令牌与完整下载地址应排除。审计查询本身也要授权,并约定保留、访问追踪和篡改检测方式。OWASP 日志指导
把同一份可见范围用于查询与导出
假设客服只负责门店 A。订单列表的范围应先限制为该租户下且属于 A 的订单,再应用搜索条件和分页。订单详情按同一范围读取。若详情接口能查到门店 B,列表隐藏它并不能提供安全边界。汇总和错误响应也需要一起检查,例如不能通过搜索某个订单号时返回的总条数推断另一门店是否有这笔订单。
下载已有文件时要比较的是文件内的数据范围与当前可见范围。客服生成文件后失去门店 A 的权限,即使仍能访问任务列表,也不能得到原文件。界面可以展示不敏感的任务摘要并说明需重新生成。代理交付的下载入口先授权再读取存储对象,缓存策略也要避免授权后的响应被其他主体复用。
明确撤权和执行之间的临界点
以排队退款为例,记录任务受理时间、最后一次授权检查时间、请求发往渠道的时间和最终状态。管理员在 10:01 撤权,任务在 10:02 开始,如果规则要求执行时仍有权限,就应停止。如果请求在 10:00 已进入渠道,撤权无法自动让外部效果消失,系统需要继续确认结果,并在审计中标明已越过的执行边界。
| 任务位置 | 执行时授权策略下的处理 |
|---|---|
| 尚未取得执行资格 | 重新授权,拒绝时结束任务 |
| 已取得资格,尚未写入本地 | 在受保护的写入边界再次校验 |
| 部分批次已提交 | 停止剩余批次,已执行项保留结果 |
| 外部渠道已接收 | 查询最终结果,按业务规则补偿或结案 |
要在本地实现严格的撤权顺序,可以让权限变更和敏感业务写入使用同一可校验的授权版本,并对竞争事务定义谁先提交谁生效。如果两者由不同系统管理,设计时要写明如何获得足够新鲜的判断,以及依赖不可用时采用什么规则。审计记录使用的授权版本,帮助区分策略配置错误和过期缓存。
设计一条从审批到结果的审计链
审计至少能把申请、审批、实际执行与后续人工调整连起来。代理执行时分别记录发起人、审批人、服务身份和被代理身份。事件可通过业务操作 ID 关联,具体用户标识仅给有权审计的人查看。不要为方便排查把退款凭证全文、访问令牌或直连下载链接写入日志。
验收越权和撤权时序
用甲的会话直接读取乙的订单、任务状态和文件,检查响应及统计都不泄露乙的数据。再在任务排队时撤权,记录哪一刻开始生效、任务最终进入哪个状态。最后验证撤权后的旧页面、缓存命中和已签发链接。审批后由系统继续执行的业务应有单独样例,审计应能还原依据和真实主体。
总结
同一套授权约束要沿查询、写入、队列和文件交付传递。明确权限在哪个时点判断,再通过审计把发起人、执行身份与业务结果连起来。