题干与适用场景
多个业务服务都要判断“谁能对哪个资源做什么”。题目要求你设计一个共享决策服务,输入主体、动作、资源和上下文,输出 ALLOW 或 DENY,同时支持用户与服务账号、层级资源、租户边界和策略变更。
本文采用一个可复算的面试假设:初始规模为 100,000 次授权检查/秒,峰值为平均值的 3 倍;99 分位延迟目标为 20 毫秒;策略写入远少于读取;调用方必须能区分“明确拒绝”和“授权服务暂时不可用”。这些数字是题目假设,不是行业基准。支付扣款、密钥签发和业务数据库写入不在服务职责内。
面试官考察点
强回答会把权限问题拆成四元组和一致性契约,而不是只说“放一个 RBAC 服务”:
- 主体、资源、动作、上下文的建模边界,以及层级资源和跨租户引用如何表达;
- ACL、RBAC、ABAC 或关系型策略的适用条件,如何避免规则散落在每个业务服务;
- 决策读取路径、策略写入路径、缓存和失效通知如何组合;
- 授权变更在什么时间内对读取可见,旧缓存是否可能放行;
- 拒绝、超时、依赖不可用时的 fail-open 或 fail-closed 选择;
- 日志、解释信息、回放和故障注入如何证明“拒绝正确且没有跨租户泄露”。
回答前需要澄清的问题
先问会改变架构的约束:
- 一致性窗口是多少? 如果撤销权限后必须在 5 秒内生效,缓存租约和版本令牌都要围绕 5 秒设计;若允许分钟级延迟,可使用更长 TTL。
- 决策是单资源还是关系查询? “用户能否读取文档”可能依赖成员关系、群组继承和父资源;这决定是否需要关系图或批量检查。
- 拒绝服务时允许什么? 公开内容可选择 fail-open,支付和个人资料通常必须 fail-closed;答案要按风险而非习惯选择。
- 策略由谁写、如何审核? 若业务团队可直接发布规则,需要版本、审批、静态检查和回滚;若只有安全团队写入,可缩小控制面。
- 是否需要可解释结果? 调试可能需要命中的规则 ID,但返回给客户端的解释不能泄露其他租户的资源或策略。
30 秒回答框架
可以先这样回答:
“我会把请求建模为 (principal, action, resource, context),由策略引擎返回允许、拒绝、策略版本和可选的内部规则 ID。控制面管理租户隔离的策略与关系数据,数据面用版本化快照和本地只读缓存完成低延迟检查;策略发布产生失效事件,调用方携带决策版本避免旧缓存覆盖新授权。安全敏感路径在服务不可用时拒绝,低风险读取按明确的业务策略降级。最后用撤销传播、跨租户、缓存竞态和依赖故障注入验证一致性与审计完整性。”
这段回答先给模型、数据流、一致性和失败路径,再由面试官选择一个方向深入。
分步骤深入解答
先定策略语义。 主体可以是用户、服务账号或组;资源可以是租户、项目、文档等层级对象;动作应是有限集合,如 read、write、share。上下文包含设备状态、请求来源或时间窗口,但不能把未经验证的客户端字段当作信任事实。ABAC 指引把主体、对象和环境属性作为决策输入;关系型策略则把“成员关系”直接表达为可遍历边。
分离控制面和数据面。 控制面负责策略编辑、校验、审批、版本和回滚;数据面只加载已发布的不可变快照并回答检查。业务服务不应各自复制一套策略解析器。策略发布后生成单调递增版本号和失效事件,数据面按版本原子替换快照,避免半套规则可见。
设计检查接口。 一个内部接口可以是:
Check(principal, action, resource, context, min_policy_version)
-> decision, policy_version, rule_id, expires_at
BatchCheck(principal, [(action, resource, context)...])
-> decisions[]rule_id 仅用于受控日志和调试;跨租户请求不能通过错误信息探测资源存在。批量接口减少网络往返,但必须限制批大小和单次评估成本。
处理一致性与缓存。 本地缓存可满足 20 毫秒目标,但撤销权限后不能只依赖 TTL。调用方可以携带上一次观察到的策略版本;服务发现本地版本不足时从共享存储读取,或返回“暂不可判定”。失效事件要有重放游标和定期对账,避免通知丢失。对极敏感资源,可在决策中加入资源版本或短租约,使“权限版本”和“对象版本”一起被检查。
选择故障行为。 数据面无法联系控制面时,已加载且未过期的快照可以继续服务;快照过期后,支付、个人资料和管理动作应 fail-closed,公开静态内容才可能按产品规则 fail-open。超时必须是明确的 UNAVAILABLE,不能伪装成 DENY,否则业务会把基础设施故障误认为用户没有权限。
隔离租户与防滥用。 每个策略对象和缓存键都带租户 ID;服务端从已验证的身份取得租户,不接受请求体中的任意租户字段。对单主体、单租户和批量请求设配额,限制递归关系深度和策略复杂度,防止一个租户耗尽评估资源。
审计和验证。 记录请求哈希、主体、动作、资源类型、决策、策略版本、延迟和失败原因;敏感值只保留不可逆标识。测试应覆盖撤销后旧缓存、组继承循环、跨租户资源 ID、策略发布半途崩溃、失效事件重复或丢失、数据面重启和依赖超时。验收不仅是“返回了 DENY”,还要能重放同一策略版本并解释原因。
估算瓶颈。 100,000 次/秒平均、峰值 300,000 次/秒时,若每次请求平均 2 KB,入口流量约为 600 MB/秒峰值。把完整策略放进每次网络请求会放大延迟和带宽,因此优先使用本地快照、批量检查和只读副本;当关系图遍历成为瓶颈,再按租户或资源分片,并限制深度和 fan-out。估算是设计假设,面试中应说明如何用压测替换它。
高质量示范回答
“我会先确认撤销传播目标和故障时的安全边界。假设系统峰值 300,000 次检查/秒、99 分位 20 毫秒,并要求撤销在 5 秒内生效,我会把控制面和数据面分开。控制面保存带租户边界的 ACL、组关系和 ABAC 条件,发布前做语法、循环和跨租户检查,生成不可变策略快照与单调版本。数据面在每个区域加载快照,先用本地只读缓存评估 (主体、动作、资源、上下文),策略失效事件带游标并可重放;调用方可传入 minpolicyversion,本地版本落后就读取共享副本,不能满足时返回 UNAVAILABLE。
对支付、个人资料和管理动作,快照过期或依赖不可用时我会拒绝;公开内容是否降级由产品明确批准。每次结果携带策略版本和内部规则 ID,日志记录决策、延迟和错误类别,但不回显其他租户信息。资源版本或短租约用于最敏感对象,避免权限已撤销而对象仍被旧决策放行。压测会覆盖峰值流量和最深关系遍历,故障注入会覆盖失效事件丢失、撤销竞态、跨租户 ID 和区域重启;通过对账和按版本回放证明没有无法解释的允许结果。”
这份回答把假设、模型、一致性、失败处理和验证连在一起。面试时应根据追问删减细节,不要把 300,000 次/秒或 5 秒当成通用生产结论。
常见错误
- 把所有问题都归为 RBAC。 角色不能自然表达资源层级、关系继承和环境条件;先说明权限语义,再选择模型。
- 用固定 TTL 解决撤销。 TTL 只能限制最坏陈旧时间,通知丢失和版本竞态仍然存在;补上版本、失效重放和对账。
- 故障时一律放行或一律拒绝。 不同资源的风险不同;按安全底线、快照新鲜度和业务可逆性定义策略。
- 把策略复制到每个服务。 多套解析器会产生漂移和不同拒绝语义;把策略版本化并让服务调用统一决策接口。
- 返回“资源不存在”或详细规则。 这会泄露跨租户信息;对外采用稳定错误码,对内受控日志记录规则 ID。
- 只测正常允许路径。 权限系统最容易在撤销、重放、循环和跨租户边界出错;必须用故障注入和版本回放验证。
追问及应对
如果业务要求撤销后立即生效,仍能使用本地缓存吗?
可以,但缓存不再单独决定结果。让撤销产生全局可观察的版本或租约失效信号;敏感请求携带最小策略版本,版本落后时绕过本地缓存或等待确认。若无法在预算内确认,就返回 UNAVAILABLE 或拒绝,不能静默使用旧允许结果。
如果一个资源属于多个父组,关系遍历出现循环怎么办?
策略发布阶段拒绝循环,运行时仍设置最大深度、节点数和时间预算。评估器维护访问集合,重复节点直接停止该分支并返回可诊断的内部错误;不要让循环把一次检查变成无限工作。
如何证明不会发生跨租户越权?
把租户作为不可省略的策略命名空间和缓存键前缀,主体租户由服务端身份推导。用属性测试生成不同租户的相同资源 ID,验证任何允许结果都满足主体、资源和策略租户一致;再做日志抽样和拒绝路径回放。
Zanzibar 的外部一致性是否必须照搬?
不必。先根据撤销延迟和业务风险选择版本令牌、租约或更强一致性。若产品要求“用户刚移除的成员关系不能继续读取旧对象”,因果顺序和对象版本就值得采用;若是低风险公开内容,较弱的一致性和更长缓存可能更简单。关键是明确承诺与代价。