返回博客

承包商分离:两层而非一层

当多个主体共享一个平台时——无论是员工、分包商,甚至是竞争对手——仅靠身份验证并不足以实现隔离。以下是我们关于如何构建这种隔离机制的思路,这种机制能够真正经得起考验。

authorizationmulti-tenantcontractor-separationsecurity

情况

我们合作的组织中,越来越多地采用共享平台,多个主体在同一套基础资产数据上开展工作。例如,一家公用事业公司可能有内部团队、一家总承包商以及两三家分包商,它们都在使用同一个网络。一个市政部门可能既有自己的员工,也有外部工程公司。竞争对手最终在同一个应用程序内协作,这种情况已司空见惯,且日益难以避免。

显而易见的问题是:如何防止这些用户看到彼此的工作内容。最直观的第一个答案是“给每个人分配一个登录账号”。这样虽然实现了身份验证——这是必要的,但还不够。身份验证只是告诉系统你是谁,却无法告知系统你被允许查看哪些内容。如果没有第二层控制,那么每个登录的用户都能看到所有内容。

那些不太站得住脚的方法

有四种常见的方法,每种都有其适用之处。但每种方法也都有其局限性,这一点值得坦诚面对。

独立租户部署。 为每位承包商提供应用程序的独立副本、独立数据库以及独立环境。这才是真正的隔离,在某些情况下这是最佳方案——通常是当各方之间没有任何共享内容且永远不会进行协作时。但这种方式成本会迅速攀升,如果平台的初衷是协作、汇总报告或建立单一数据源,那么这种做法就违背了平台存在的意义。 如果大部分数据是共享的,只有一小部分是敏感的,那么独立租户方案就显得过度了。

仅隐藏用户界面。 这是最常见的应对措施。隐藏按钮、跳过菜单项、过滤列表视图。这不过是“安全表演”。任何掌握浏览器开发者工具、能直接发起 API 请求或进行精准导出的人,都能获取这些“隐藏”的数据。记录依然存在,依然在网络中传输,依然会出现在批量导出中。这只能防止诚实的用户无意中发现某些内容;对于真正想找的人来说,这根本起不到阻挡作用。

应用层过滤,逐个端点实施。 这确实是一个进步:在代码中强制执行过滤规则,凡是查询数据的地方都如此。这种方法确实有效——直到某天失效为止。如今,安全机制分散在数十个查询点中。每个新功能都可能成为漏洞,每次重构都可能遗漏某个点。这种方法在发布之初往往是正确的,但随着代码库的增长,正确性会逐渐偏离。

导出到独立的工作区。 将每位承包商所需的数据复制到只有他们可见的工作区中。这种做法确实实现了数据隔离,但副本在生成的一瞬间就会过时。此时,问题就从可见性问题变成了同步问题,现场工作人员最终看到的将是今天工作的昨日版本。

这些方法并非在所有情况下都是错误的。但作为共享平台上多方访问的通用解决方案,它们是不合适的。

双层模型

我们的方法将授权分为两个相互独立的层,它们共同构成系统。

**管理员权限具有开放性。**它们规定了用户被允许执行的操作——例如创建工单、编辑报告、删除库存商品。如果没有相应的管理员权限,默认情况下您只能查看数据,而无法对其进行修改。

**角色限制具有排他性。**它们规定了用户完全无法查看或操作的内容。每条限制都指定了一个模型(点、报告、任务)、该模型上的一个字段(所有者、状态、类别)、一个比较条件以及一个值。对于该角色的成员而言,匹配的记录在执行指定操作(读取、编辑、创建、删除)时会被独立隐藏。

这些层之间互不干扰。一名现场工作人员可以拥有编辑报告的权限,而角色限制会隐藏任何非其本人编写的报告——因此他们可以进行编辑,但仅限于自己的报告。管理员权限授予了该能力;角色限制则缩小了范围。这两层之间无需相互关联,而正是这种独立性,才是该模型经久不衰的主要原因。

为什么服务器端字段级强制执行至关重要

角色限制是在数据离开数据库之前,由服务器端执行的。这在实际应用中至关重要:

  • 该过滤器适用于所有查询路径——详情页、列表视图、API 调用、批量导出、地图图块。
  • 用户若在 URL 中输入其偶然知晓的记录 ID,将收到 404 Not Found 错误,因为对系统而言该记录确实不存在。
  • 其他用户的屏幕截图毫无用处;当受限用户尝试访问该页面时,数据将无法加载。
  • 新功能会自动继承这些限制,因为它们都经过相同的服务器数据层。

UI 层面的隐藏功能在所有这些测试中均告失败。应用层过滤仅在开发者记得在每个新的查询位置应用过滤器时才能通过测试。而在服务器端实施的字段级限制,则将问题从“我们是否记得?”转变为“数据是否符合规则?”——这正是我们真正希望系统能够回答的问题。

具体示例

同一光纤建设项目中有两家承包商。两家都使用同一张地图,都调用相同的共享底图,并各自针对自己的工作生成日志报告。

创建一个名为 承包商 A 的角色,并设置以下唯一限制:

  • 模型:Point
  • 字段:owner
  • 比较条件:=
  • 筛选值:承包商 B
  • 被阻止的权限:读取、编辑、创建、删除

将承包商 A 的用户添加为成员。对 承包商 B 执行相同的操作。这就是全部配置。

承包商A的团队现在只能看到自己的点位、共享图层,而看不到承包商B的任何内容。如果他们打开点位列表,其中不会包含承包商B的记录。如果他们导出为CSV格式,导出文件会被过滤。如果他们猜测承包商B某个点位的数字ID并将其粘贴到URL中,会收到404错误。承包商B的情况则完全相反。 双方均无法知晓对方已完成多少工作、工作位置以及更新时间。

双方都能看到这些共享的基础设施——电缆线路、电线杆、管道设施——因为这些设施不受任何限制。在需要协作的地方保持协作;在需要隔离的地方强制隔离。该平台无需在这两者之间做出选择。

何时选择独立租户仍是最佳方案

两层模型并非万能之策。如果双方没有任何资源共享,从不合作,从未发布过联合报告,且出于监管原因必须使用物理上分离的基础设施,那么采用独立租户是明智之选。两层模型所替代的,是更为常见的情况:即各方共享平台的大部分资源,但需要隐藏特定部分。在这种情况下,采用独立租户不仅会浪费资金、破坏报告机制并拖慢工作进度,而仅在用户界面层面进行隐藏则会导致信息泄露。

我们的判断标准很简单:如果各方能够通过查看相同的底图、运行相同的报表以及在同一张地图上协作而受益,那么他们就应该使用同一个平台——同时确保在实际可行的层面上严格实现功能隔离。

摘要

  • 在公用事业、电信和市政运营领域,多方在共享平台上的协作日益普遍,且参与方中越来越多地包含彼此竞争的各方。
  • 仅靠身份验证无法隔离用户;它只能识别用户身份。除非增加第二层防护,否则所有登录用户仍能查看所有内容。
  • 独立租户部署虽能实现真正的隔离,但在各方需要就大部分数据进行协作时,却违背了共享平台的初衷。
  • 仅在用户界面层隐藏数据只是“安全作秀”——数据依然存在,且可通过开发者工具、API 或导出功能获取。
  • 应用层过滤效果虽好,但随着代码库的增长和新功能的添加,往往会逐渐偏离正确性。
  • 双层模型将授权分为宽松的管理员权限(可执行操作)和严格的角色限制(不可查看的内容),并在服务器端以字段为单位进行强制执行。
  • 服务器端的字段级强制执行意味着,从受限用户的视角来看,受限数据确实不存在——无论是在 UI 中、API 中、导出文件中,还是在猜测的 URL 中。
  • 实际配置方案是每位承包商仅分配一个角色,从而在需要隔离的地方实现隔离,同时在需要协作的地方保留共享层。
  • 当各方没有任何共享内容时,独立租户仍有其存在的价值;但在更常见的情况下——即各方共享大部分内容但需要隐藏其中一部分时,两层模型才是更好的解决方案。