概述
现在,既然您已经对多租户的整体术语和架构有了初步了解,让我们来探讨如何将这些概念应用到实际解决方案中。关键问题是:从哪里开始?许多团队都会向我提出这个问题。幸运的是,我认为在这个领域有一个相对统一的答案。无论您是进行迁移还是从头开始构建,我都会建议您从入驻、身份验证和控制平面作为构建大多数多租户架构的起点。这些元素会强制在您的环境中引入重要的基础构造,定义租户如何被引入以及用户如何被创建并绑定到租户。这些初始步骤将开始建立控制平面的基础模块。
从这里开始,多租户将被置于核心位置。这意味着架构的每一层都必须具备多租户意识。系统中的每个组件都必须考虑租户如何影响其设计和实现。虽然这可能看似微小的细节,但其影响深远。租户的存在会影响您如何隔离租户、如何表示其数据、如何支持多个角色、如何计费,以及解决方案的其他诸多方面。它还开始明确控制平面与应用程序平面的边界。目标是避免陷入从应用程序开始,事后再添加多租户支持的陷阱。这种做法通常效果不佳,并会导致大量重构和妥协,从而削弱SaaS架构的设计。
本章将首先探讨如何搭建控制平面基础架构。这里将涉及部署支撑各类服务的底层基础设施与资源。尽管控制平面最终将承载众多服务,但本章主要聚焦于用户接入与身份管理功能。随后,我们将探讨如何扩展控制平面其他方面的构建。
在深入探讨注册流程时,您将对整个流程中的各个组成部分有更清晰的认识。对于某些环境,该流程的编排可能相当复杂。尽管不同SaaS环境中的注册流程可能存在差异,但仍存在一些贯穿多个实现方案的共同主题。我将通过一个示例注册流程,逐一探讨这些主题。这将帮助您理解构建自有注册服务时需要考虑的关键因素,并突出注册在SaaS架构中扮演的至关重要角色。
接下来,我将审查身份验证部分。我将深入探讨如何将单个用户与租户绑定,从而形成第一章中提到的“租户上下文”概念。这将包括对具体身份验证机制的深入分析,这些机制使我们能够定义租户的身份验证方式,并将租户上下文注入流经SaaS应用程序所有后端服务的请求中。我们将看到这种上下文如何最终影响团队构建和管理SaaS架构中多租户功能的方式。
综合考虑这些基础概念,您将更清晰地认识到在初期阶段解决这些问题的重要性。我们的目标是向您介绍关键策略、模式和考虑因素,而不会过多深入任何特定技术细节。掌握这些核心概念将为您在后续章节中探讨的许多多租户相关主题提供洞察力。
创建基础环境
为了开启这一旅程,我希望以从零开始的态度来对待入驻流程和身份管理。这将帮助您更好地理解如何从头开始实施这些策略。这意味着我们需要暂时搁置入驻流程和身份管理的具体细节,首先思考在开始为租户提供入驻服务之前,我们需要建立哪些基础架构。支持入驻流程的服务运行在控制平面内,因此我们需要首先搭建所有必要组件,以确保支持入驻和身份管理的控制平面微服务能够正常运行。
基础设施的创建、其依赖资源以及控制平面的搭建,就是我所说的创建基础环境。我们需要创建脚本和自动化流程,以便能够快速部署所有用于托管SaaS环境的必要组件。虽然我们的目标是让入驻和身份验证功能正常运行,但基础环境的范围包括在开始入驻前为多租户环境一次性配置的所有资源。这意味着我们将设置一些超出租户入驻和身份验证的范围。我们目前不会关注这些其他部分,但需要明确的是,基础环境包含了所有这些概念。
我们的基础环境的实际构建是通过经典的DevOps模型实现的,利用基础设施自动化工具来创建、配置和部署基础环境所需的所有资源。图4-1展示了这一过程的高级概念化视图。
图4-1
基本思路是选择适合您环境的DevOps工具,并创建一个单一、可重复的自动化模型,该模型能够配置所有必要组件,将环境迁移至可开始接入租户的状态。
当然,基础环境的具体内容会因您所使用的SaaS解决方案的技术栈类型而大不相同。例如,Kubernetes技术栈与无服务器技术栈的实现方式可能截然不同。不同云服务提供商的细节差异也会影响资源预配置流程。我们将通过具体示例来探讨这些细节,但目前我们先提升视角,仅聚焦于本步骤中需要进行的配置,以确保系统能够开始接入租户。
创建你的基础环境
为了更好地理解基础环境包含的内容,让我们看看可能被配置、部署到基础环境中的组件示例。如图4-2所示,我已整理出基础环境中可能创建的组件和基础设施的概念视图。目标是展示一些核心基础设施概念,而不会过多陷入特定技术的细节。
图4-2
在图4-2的中间部分,您可以看到我已经创建了托管多租户SaaS环境所需的网络设施。在此示例中,我仅使用了一些常见的AWS网络组件(VPC、可用区和一些子网)来表示将托管SaaS环境的高可用性网络。这些相同的网络组件可以映射到任何其他技术。当前阶段的关键在于,配置和搭建此基础环境时,您需要为控制平面(以及潜在的租户)预配置并设置所有核心网络构件。
在此网络中,我还展示了控制平面的部署。由于控制平面被所有租户共享,因此可作为基础环境配置的一部分进行配置和部署。控制平面必须就位,以便我们开始租户接入并建立其身份。为简化示例,此处仅列出了部分服务。实际中,控制平面服务列表将涵盖更广泛的范围。我们将在深入探讨具体解决方案时,对这些服务进行更详细的说明。
在图4-2的右下角,您还会看到一组共享资源。这些项代表了可能被租户共享的任何资源的概念占位符。通常,如果您有将被所有租户共享的共享资源,可以在基础环境的设置过程中进行预配置(因为它们无需在接入过程中创建)。存储通常是一个很好的示例,假设您有一个用于共享的数据库。
如果微服务在您的解决方案中,那它应该是共享的,它可能在基础环境预配置时创建。您还会看到共享的身份验证和消息队列的设置。在本文后面的深入探讨微服务入门体验时,我们会详细讨论。
最后,在右上角,我显示了系统管理员身份和管理控制台的占位符。这些代表登录您创建的专用工具来支持、更新、配置和管理多租户架构状态的用户。系统管理控制台作为SaaS环境的统一管理界面,为团队提供了一套专为多租户环境设计的必要功能和能力集合;它将与其他提供通用功能的现成解决方案配合使用。即使借助这些工具,大多数SaaS团队仍需开发自己的定制管理应用程序,以满足环境的特定多租户需求。
图4-3展示了一个简单的SaaS管理控制台应用程序的快照,以帮助您更好地理解这一概念。通过此应用程序,您可以访问有关SaaS解决方案的所有核心信息。您将能够监控租户的注册状态、激活/停用租户、管理租户策略、查看租户/层级指标,以及任何其他用于管理和运营SaaS解决方案所需的功能。该应用程序必须作为基础环境设置的一部分进行配置和部署。
图4-3
值得注意的是,部分团队倾向于在管理控制台方面投入不足,更倾向于采用现成解决方案而非自行开发。总体而言,这种权衡通常并不划算。虽然可以通过第三方解决方案构建控制台体验,但某些特定操作、洞察以及配置选项只能通过创建针对性体验来有效解决。
创建和管理系统管理员身份
在设置基础环境并配置管理应用程序时,你会发现,你的部署流程还必须设置系统管理员身份模型。每次触发基础环境创建时,您都需要提供初始管理用户的配置文件,该用户将能够登录管理控制台。此身份的创建与租户身份的创建完全独立。这意味着您需要为这些系统管理员用户提供完全独立的身份验证体验,以便他们能够访问管理控制台或您用于管理多租户环境的任何命令行工具。
为了支持系统管理员身份,您需要一个能够拥有并验证这些用户的身份提供商。您在此处使用的身份提供商可以是与租户身份相同的身份提供商,也可以是作为更广泛企业管理策略一部分的独立身份提供商。无论使用哪种身份提供商,引入系统管理员身份的基本机制将非常相似。
关键要点是,您需要在基础配置自动化流程中添加一些步骤,以创建并配置系统管理员身份模型。该自动化流程将包括身份提供商的创建与配置,以及初始系统管理员用户的创建。一旦该用户配置完成,您即可使用此身份访问系统管理控制台。进入系统管理控制台后,您将能够管理并创建更多系统管理用户。
图4-3展示了系统管理员用户管理视图。在此示例中,我已在环境配置完成后访问并认证进入控制台。现在,我可以使用同一页面创建和管理其他系统管理员用户。
从管理控制台触发入驻流程
在创建系统管理员用户并确保管理控制台正常运行后,您已具备创建和引导租户的所有必要条件。在最终版本的解决方案中,引导流程可作为自助服务体验的一部分,也可由内部流程驱动。显然,如果这是由内部流程驱动的,则应使用系统管理控制台来管理入驻流程。这意味着控制台中需要有一个操作,在触发入驻操作前收集新租户所需的所有数据。
一些团队发现,能够从系统管理控制台直接进行租户接入具有很大价值。即使接入流程最终采用自助服务模式,您仍可通过管理控制台测试和验证接入体验。这对于验证和测试应用程序接入流程的团队尤为有用。
控制平面预配置选项
在图4-2中,我展示了控制平面被部署到与租户环境相同的设施中。这是一个完全可行的方案。然而,值得注意的是,控制平面的部署方式和位置会根据环境需求以及多租户架构所采用的技术栈而有所不同。以Kubernetes为例,我可以为控制平面创建一个独立的命名空间,将租户环境与控制平面部署在同一集群和网络基础设施中。我也可以选择将控制平面部署在完全独立的基础设施中,该基础设施专用于控制平面。
图4-4展示了这两种选项的概念视图。左侧是共享控制平面模型,其中控制平面与租户基础设施部署在同一环境中。右侧则展示了一种将控制平面部署到独立环境的方案。在此方案中,租户运行在完全独立的网络或集群中,从而在控制平面与应用程序平面之间划出更明确的界限。
图4-4
这两种选择的权衡非常明确。如果你希望对这些环境进行完全独立的扩展、管理和运维,那么选择专用控制平面环境可能是更好的选择。合规性也可能是一个考虑因素;你的业务需求或领域可能更适合通过在控制平面和应用程序平面之间设置更严格的边界来实现。当然,将控制平面与应用程序平面放在同一环境中确实会简化一些事情。它减少了需要管理、配置和提供的组件数量,也可能降低成本。如果你选择专用模型,你需要决定如何集成这些独立的构造,以使控制平面能够与应用程序平面交互。
技术栈选择也可能影响控制平面部署方式。例如,部分团队可能为控制平面和应用程序平面选择不同技术栈。我可能选择无服务器架构作为控制平面,容器作为应用程序平面。这可能使您更倾向于采用专用控制平面模型。
入驻体验
现在,我们的基础环境已配置完毕,我们可以将注意力转向租户的入驻流程。正是通过入驻流程,您将建立并验证多租户架构中的一些最基础的要素。事实上,在处理新项目或迁移至SaaS环境的客户时,我总是建议他们将初始关注点放在入驻流程上。
从这里开始,团队必须回答许多将影响并塑造其整个SaaS架构的艰难问题。入驻不仅仅是创建一个租户。它涉及创建并配置支撑该新租户所需的所有基础设施组件。在某些情况下,这可能是一个相对简单的任务,而在其他情况下,可能需要编写大量代码来协调入驻流程中的每个步骤。租户的分层方式、身份验证机制、策略管理方式、隔离配置方式、路由规则——这些都是多租户环境中客户入驻体验所涉及的关键领域。
入驻是服务的一部分
许多团队陷入将入驻视为在系统建成后附加的组件的误区。他们会创建占位符和临时解决方案来模拟入驻体验,认为可以在后续流程中“真正实现”。在SaaS环境中,入驻体验并非被视为某种脚本或自动化流程,而是被视为服务范围之外的独立组件。相反,它是SaaS体验中最基础的组成部分之一,确保其设计得当应是构建多租户解决方案团队的核心要务。
入驻流程恰好处于业务与技术优先级的交汇点。每位客户在入驻过程中的体验将对业务整体成功产生深远影响。该流程的顺畅度、效率及可靠性将直接影响客户对产品体验的感知。
产品体验的质量是留下第一印象的绝佳机会。入驻流程体验还直接关联“价值实现时间”(Time to Value),即客户从注册到在您的SaaS产品中实现实际生产力和价值所需的时间。此过程中出现的任何额外摩擦都将影响您作为服务提供商的印象,并可能影响您将客户从采用者转化为推广者的能力。
入驻流程也是部署、身份验证、路由和分层策略的实施阶段。例如,租户如何被隔离和池化,需要通过入驻流程直接体现和实现。租户的身份验证方式将在入驻流程中配置并应用。租户基于其分层和部署模型进行上下文路由的配置,也将在入驻流程范围内完成。SaaS架构中的许多设计决策,最终都将通过入驻流程得以体现。从许多方面来看,您的入驻配置、自动化和部署代码将处于实现SaaS环境中多租户策略的核心位置。
自动化入驻流程所需的努力和代码量可能会让一些团队感到惊讶。SaaS团队往往会低估构建一个自动化入驻体验所需的努力和投资。入驻流程是多租户环境中最基本的核心要素之一,正是通过入驻流程,才能实现对SaaS业务至关重要的运营效率和敏捷性目标。
自助式接入与内部接入
到目前为止,关于入驻流程的讨论似乎主要描述了自助式入驻体验所采用的机制。我们许多人曾注册过无数B2C SaaS服务,只需填写表单、提交信息即可开始使用。虽然这种经典的入驻模式在我们的讨论范围内,但我们还需考虑入驻流程无法支持自助模式的场景。例如,想象一家B2B SaaS供应商,他们仅在达成协议并同意将该服务接入你的系统后才开始入驻流程,这类SaaS供应商可能仅拥有内部管理的入驻体验。
我的观点是,入驻流程并不与特定的体验绑定。您可能采用自助式入驻,也可能使用内部入驻流程。每一种SaaS解决方案,无论其入驻体验如何呈现,都必须遵循相同的价值观。在我看来,自助式入驻和内部入驻流程的标准是相同的。这两种方法都应致力于创建一个完全自动化、可重复、低摩擦的入驻流程,重点在于最大化客户的价值实现时间。是的,运营团队可能会负责内部流程的执行。然而,这并不意味着你对这个入驻流程的自动化程度、可扩展性或稳定性会有所妥协。
对于构建的任何SaaS系统,确保将入驻体验视为系统中的关键部分。它处于确保拥有一个一致、可重复、自动化的入驻机制的核心位置,该机制能确保每位新客户在无需任何手动流程或一次性配置的情况下顺利入驻。
入驻的核心组成部分
现在你对入驻流程的重要性有了更清晰的认识,让我们将关注点转向入驻体验背后核心组件的细节。虽然入驻流程的实现细节繁多,但本阶段我的目标是为你提供该流程核心组件的高层级视图,并概述通常塑造这一体验的指导原则。
图4-5
在左侧,您将看到两种常见的模式,这些模式可用于驱动入驻流程。首先,我展示了一个租户管理员通过自助注册流程进行入驻的情景,该流程可能是允许租户提交信息、选择计划并提供所需配置信息以在系统中注册为新租户的网页应用程序。我还展示了第二个入驻流程,在此示例中由系统管理员发起。这代表了SaaS提供商内部角色通过管理控制台(或其他工具)输入新租户的入驻数据并触发入驻流程。在此示例中,我包含了这两种入驻路径。然而,在大多数情况下,SaaS供应商会支持其中一种方法。我在此同时展示两种路径,旨在强调无论入驻流程的入口点如何,入驻流程本身都应是完全自动化的,适用于这两种用例。
对于这两种入驻路径,您会发现它们都会向入驻服务发送入驻请求(步骤1)。对于入驻流程,我通常更倾向于使用单一的入驻服务来负责整个入驻流程的协调。该服务负责入驻流程的完整生命周期,确保流程中的所有步骤均成功完成。这尤其重要,因为入驻流程的某些环节可能以异步方式运行,或依赖于可能存在可用性问题的第三方集成。
入驻流程随后会调用一系列分布式服务,用于创建和配置租户的设置及基础设施。入驻流程的执行顺序可能根据SaaS应用的特性而有所不同。但目前通常是,在租户激活并通知租户管理员其账户已激活之前,完成所有必要的租户资源的创建和配置。
虽然实现注册流程的方法多种多样,但需要从创建租户标识符开始。在我们的示例中,该租户标识符将通过向租户管理服务(步骤2)发送创建租户请求生成,并传递有关租户的所有信息(公司名称、身份配置、层级等)。该请求还将生成与租户关联的唯一标识符。团队通常会使用全局唯一标识符(GUID)作为租户标识符的值,避免包含任何与租户名称或其他识别信息相关的属性。这可防止任何人通过给定标识符关联到特定租户。该租户在创建时还带有“活动”状态,用于管理租户的当前状态。在租户接入场景中,初始状态将设置为false。系统创建租户后,租户将获得一个可在整个接入流程中使用的租户标识符。关于租户管理服务及其在控制平面中的作用,我将在第5章中详细说明。
租户注册流程的下一步是预配置所需的租户资源(步骤3)。对于某些多租户架构,此预配置步骤可能是注册实现中最关键的部分。例如,对于全栈孤岛部署,这可能意味着为基础设施和应用服务提供一个全新的集合。相比之下,全栈池环境可能只需进行少量基础设施配置和设置。
随着我们深入探讨更多实际案例,您可能会惊讶地发现,用于构建这一入驻体验的代码和自动化程度之高。事实上,这往往是SaaS系统模糊DevOps边界的一个领域。在传统环境中,DevOps生命周期的重点大多放在基础架构的部署和更新上,而SaaS环境则可能依赖于在每个租户入驻过程中执行DevOps代码。你的系统可能在运行时动态预配置和配置新基础设施,以隔离租户基础设施。如你所想,这为多租户解决方案的整体DevOps架构设计带来了新的考量和思维方式。对于部分团队而言,这意味着需要采用新的工具和方法来预配置租户环境。
目前,我们已创建了租户并完成了租户资源的预配置。现在可以将此新租户添加到计费系统(步骤4)。此步骤本质上是向计费系统提供识别新租户的信息,以及应用于该租户的计费模型所需的其他信息。在此假设下,在接入新租户之前,您已配置并设置了不同层级或计费计划,这些计划决定了解决方案的整体定价模型。在接入过程中,计费服务将根据租户的接入配置与预先配置的合适计费计划进行关联。
您会发现,图4-5中提到了一个独立的计费服务提供商。其核心思想是,您的计费服务将负责管理和协调与计费系统之间的任何集成。在许多情况下,该计费服务可能由第三方系统提供支持。在这种情况下,您可能会发现将一个独立的计费服务置于注册流程与计费提供商之间具有价值,这使您能够管理支持特定计费提供商所需的任何特殊考虑。在其他情况下,您可能直接从注册服务与计费提供商进行集成。值得注意的是,部分SaaS公司会使用内部计费系统。即使在这种情况下,您仍应确保入驻流程遵循类似的集成模式。关于计费的其他细节(超出入驻范围)将在第14章中详细探讨。对于入驻流程的最后一步,我们需要创建租户管理员用户(步骤5)。如您所知,租户管理员角色代表为特定租户创建的第一个用户,该租户将能够创建其他可访问系统的用户。然而,当前阶段的主要目标是通过身份提供商创建此初始用户,以便租户能够认证并访问其已分配的环境。在此,您需要借助身份提供商的特性来协调新租户的通知和验证流程。大多数身份提供商支持生成包含系统访问URL和临时密码的电子邮件,此过程将触发认证用户在登录流程中输入新密码。目标是将此注册流程的大部分自动化工作推送到身份提供商。依赖这些提供商发送电子邮件邀请、临时密码并处理密码重置。
在入驻流程中还有一个需要考虑的最后环节。之前在创建租户时(步骤2),我将租户的活跃状态设置为false。您的注册服务负责跟踪所有这些不同注册状态的状态。只有在确定每个流程均已成功完成后,它才会将租户的活动状态设置为true。这可能包括流程重试和其他故障处理策略,以应对在租户环境的预配置和配置过程中可能发生的任何故障。假设入驻成功,入驻服务现在可以调用租户管理服务并更新活动状态为true。这对于您的SaaS环境的管理控制台尤为重要,该控制台提供了用于查看和管理租户状态的功能。在入驻过程中,租户视图应显示正在入驻的任何租户的状态,并突出显示租户的活动状态。
追踪与显示入驻状态
从这个流程中可以看出,入驻流程包含了许多相互关联的环节和依赖关系。随着流程的复杂性增加,对入驻流程各阶段的详细运营洞察变得愈发重要。这对于分析进展、识别问题以及评估入驻自动化整体行为和趋势至关重要。同时,这也意味着需要确定合适的设计和工具,以有效捕获并展示解决方案的入驻特征。
至少,您可以设想为入驻流程中的每个步骤映射一组独立的状态。例如,您可以为TENANT_CREATED、TENANT_PROVISIONED、BILLING_INITIALIZED、USER_CREATED和TENANT_ACTIVATED。这些状态可通过管理控制台的租户视图进行展示,使您能够在任意时间点查看特定租户的入驻状态。
为入驻流程分配并展示状态的真正价值在于,为入驻进展的状态提供更丰富的运营洞察,这对于排查任何意外的入驻问题至关重要。准确了解入驻流程在哪个环节出现故障,对运营团队而言至关重要。尤其当入驻流程包含大量基础设施预配置时,这一点更为关键。在这种情况下,您可能需要跟踪更细粒度的状态,以了解预配置过程中的各个阶段。
基于层级的入驻
在分析入驻流程时,我概述了配置服务的作用及其在创建和配置租户环境中的角色。当考虑不同租户层级如何影响配置生命周期的实现方式时,配置过程会变得更加有趣。如您所知,我们使用层级来呈现具有不同体验的租户配置文件。这些不同的体验通常会转化为根据系统层级需要独立的基础设施和配置需求。
为了更好地理解这一点,让我们通过一个分层入驻流程的示例来进行说明。图4-6展示了一个支持两个独立层级(基础和高级)的环境视图。
图4-6
我已将视图缩小,仅聚焦于控制平面中的预配置服务。每当入驻服务触发此预配置服务时,它会提供包含新租户关联层级的租户上下文信息。当预配置服务接收到此请求时,它将评估层级并确定所选层级将如何影响支持租户环境所需的配置和基础设施。在此示例中,我们的SaaS解决方案为高层级租户提供全栈隔离部署模式,每个租户均拥有完全专属的资源。这意味着每次入驻事件都需要自动化预配置这些完整的租户栈。而基础层级租户则被入驻到全栈池模型中,其中所有基础设施由多个租户共享。在此模式下,入驻流程将更为轻量级,仅需通过配置增强来添加对新租户的支持。
这些全栈部署模式具有截然不同的入驻体验,且相对易于理解。当采用混合部署模式时,情况会更加有趣。在混合模式部署中,资源以更细粒度的粒度进行隔离和池化。这意味着入驻流程需要根据资源所属的隔离区或池配置,应用相应的层级入驻策略。图4-7展示了混合模式部署如何影响您的资源分配流程。
图4-7
我故意将此架构设计得较为复杂。这里展示了相同的预配置服务,但现在它需要考虑更多因素,因为每个租户在入驻时都有不同需求。让我们从图4-7的左侧开始,您会看到我为租户1和租户2分别部署了两个服务。因此,对于每个高级层级的租户,您的预配置服务需要以完全隔离的模型配置并部署履行和订单服务。这两个微服务的存储和计算资源完全隔离。
现在,尽管这两个微服务是为高级别租户单独部署的,但这些相同的微服务也被基本级别租户所使用。这一点在图的中间部分有所体现,我标出了由所有非高级别租户(在本例中为租户3至N)共享的履行和订单微服务的共享版本。这意味着预配置服务必须对这些服务进行一次性的配置和部署,以支持共享租户。一旦这些服务正常运行,预配置服务为每个新租户的任务将减少。您可能需要配置路由或设置一些策略,但大部分繁重工作将在初始配置和部署这些服务后完成。
最后,在图4-7环境的右侧,您将看到一系列以不同部署模型部署的服务,以满足高级和基础级租户的需求。在此处选择隔离或池化模式更多是基于多租户架构的通用需求(而非租户级别)。核心思想是,您应根据一组全局需求(如噪声邻居、合规性等)来选择隔离区和池选项。
在此示例中,我故意在这些服务中引入了一些差异,以突出您在租户入驻过程中可能需要支持的用例。例如,产品微服务为所有租户使用独立的计算资源;这就是为什么您会看到针对租户1–3的独立服务实例。然而,您还会发现该服务使用了共享存储。这为入驻流程增添了新的复杂性。现在,您的预配置服务必须处理这种差异,为所有租户一次性预配置存储,同时在每个租户入驻时预配置和部署产品微服务的独立实例。
其他服务(评分和购物车)仅用于展示在实现预配置服务时可能遇到的其他模式。评分服务完全使用共享计算和存储,而购物车微服务则使用共享计算和独立存储。支持这些服务的入驻流程关键在于明确哪些资源是隔离的、哪些是共享的,并根据上下文触发相应资源的创建和配置。这与我们在第3章中讨论的混合部署模式相呼应。不过,这里我们关注的是混合部署模式如何影响多租户环境的入驻体验。
在资源池化资源的配置时机方面,一个常见的问题是其在入驻流程中的总体时间安排。由于这些资源仅需配置和部署一次,许多人可能更倾向于在整个多租户环境的初始设置阶段预先配置这些资源。因此,如果您正在搭建一个全新的基础环境,可以选择在此阶段一次性配置所有资源池化资源。在我看来,这似乎是更自然的方法。这可能意味着您的资源分配服务需要支持一个独立的调用路径,由DevOps工具触发以完成这些资源的一次性创建。随后,随着每个新租户的接入,共享基础设施已提前就位。
另一种选择是延迟创建这些共享资源,并在第一个租户加入时触发其创建(类似于懒加载模式)。虽然这可能会减慢租户加入过程,但此过程的开销仅由第一个租户承担。我个人倾向于预先分配这些资源。然而,其他因素可能使您倾向于选择其中一种策略。
虽然支持这些基于层级的部署模型可能既有趣又强大,但同样重要的是要考虑您的入驻流程复杂性可能对我们整体SaaS环境的复杂性产生的影响。是的,您希望为业务提供丰富的工具以支持不同租户配置。同时,您不希望在此处过度定制。此外,需特别强调的是,这仍属于分层级定制范畴。切勿将此机制视为支持针对单个租户进行一次性定制的手段。
追踪入驻资源
如果您的入驻流程需要为租户分配专用资源,则还需考虑多租户环境如何跟踪和识别这些资源。您会发现,系统中的其他组件最终需要定位并访问这些租户专属资源。
为了更好地理解我的意思,让我们考虑一个更具体的示例。假设您已按照图4-7所示的混合部署模型接入了租户。该模型包含大量专用资源和共享资源的示例。现在,假设您将一个高级租户接入该环境,并创建了支持该租户所需的专用资源。
一旦入驻完成且租户正常运行,您仍需向该环境部署更新。补丁、新功能及其他变更在应用程序生命周期内均需进行部署。此时情况将变得稍显复杂。由于我们采用混合部署模式,无法通过部署到单一静态位置来更新系统。例如,假设要发布订单服务的新版本。为了部署新代码,您的DevOps团队需要找到订单服务在不同资源中的独立部署实例,这些资源是由入驻流程预配置的。具体来说,这意味着需要将订单服务部署到租户1和租户2的高级别的独立实例,以及由其他租户共享的基本级别共享实例。
因此,这引发了一个问题:您的部署流程如何处理这种情况?它如何知道每个租户的资源是独立的?要使这套机制正常工作,唯一的方法是在用户注册过程中捕获并记录这些租户专用资源的位置和标识。虽然对这些跟踪信息的需求显而易见,但目前尚无明确或通用的策略来解决这一问题。一些团队会在新租户入驻时将数据存储在表中,并在部署过程中引用该表。另一些团队可能利用其DevOps工具链的组件来解决这一挑战。核心要点是:如果你的入驻流程为租户分配了专用资源,就必须捕获并记录这些资源的信息,以便部署和运维流程的其他环节能够引用这些信息。我们将在Kubernetes(第10章)和无服务器(第11章)环境章节开始探讨。
处理入驻失败
入驻流程中的任何失败都可能对SaaS提供商构成重大问题。然而,在具有自助式入驻体验的多租户环境中,这些失败的重要性进一步凸显。入驻是您与租户建立的第一印象,入驻流程中的任何失败都可能导致业务损失。
虽然您在此处的可靠性部分将来自于应用扎实的工程实践,但在入驻过程中,您对外部系统的依赖也可能影响入驻流程的稳定性。为了更好地了解可选方案,让我们通过一个具体的示例来探讨可能存在的外部依赖。图4-8展示了可能包含在入驻流程中的计费集成概念视图。
图4-8
在此示例中,假设您依赖于与第三方计费提供商的集成。通过在注册流程中包含第三方计费解决方案(这很常见),您已使注册流程的可靠性直接依赖于计费提供商的可用性。如果计费系统不可用,则您的租户注册流程也将不可用。
现在,你可能会认为这只是使用第三方解决方案带来的风险。然而,在这种情况下,你的系统很可能仍然能够继续运行——即使计费系统出现故障。虽然确实需要创建计费账户,但你的系统仍可在系统恢复后完成注册流程并配置计费设置。
在图4-8中,我突出显示了这个问题的一种潜在解决方案:将计费集成完全异步化。在此模型中,您的注册流程会通过队列请求添加新租户。计费服务随后会获取该请求,并使用异步请求尝试在计费系统中创建账户。如果该请求失败,计费服务将捕获失败并安排重试。实现容错集成有许多不同策略。不要陷入细节。关键是要创建一个与计费提供商的集成模型,使注册流程无需等待计费账户创建即可继续。对于某些场景,仅出于加快注册体验的目的,始终采用异步集成可能更优。
我之所以专注于计费,是因为它能自然地说明拥有一个容错的入驻体验的重要性。实际上,你应该全面审查入驻自动化流程中的所有环节,寻找可能导致故障或瓶颈的环节,并制定新的策略以加快或增强入驻流程的稳定性。入驻失败的成本通常很高,因此你应尽一切努力使这一机制尽可能健壮。
测试入驻体验
此时,入驻流程的重要性应已明确。该流程的潜在复杂性和涉及的多个环节使其特别容易出现错误。您应采取额外措施验证入驻流程的效率和可重复性。许多团队在构建入驻流程后,仅依赖客户的实际操作来发现可能影响入驻体验的瓶颈或设计缺陷。为解决这一问题,我始终建议团队投资构建一套丰富的入驻测试集,用于全面验证和压力测试入驻体验的各个维度。
在此过程中,您可以考虑多种测试类型。例如,您可以创建模拟不同入驻工作负载的入驻负载测试。或者,您可以创建验证系统从故障中恢复能力的测试。部分团队还会引入性能测试,以测量入驻租户所需的时间。每种测试均可结合不同租户层级执行,其中租户层级可能对应入驻流程中的不同路径。
目标是确保您的入驻体验设计、架构和自动化在实际解决方案中得到充分实现。这意味着通过模拟各种使用场景来推动规模扩展,从而检验入驻设计和实现的可行性。同时,这也将帮助您验证环境是否正确展示了用于衡量您满足已定义服务水平协议(SLAs)能力的关键指标。这里强调的不仅仅是确保最佳路径正常运行——而是确保入驻流程满足规模和可用性要求,并提供符合客户期望的服务体验。
创建SaaS身份
到目前为止,我简要提到了身份在入驻流程中的作用。然而,身份识别系统中还有许多需要深入探讨的组成部分。是的,入驻流程会建立身份,但这意味着什么?身份如何配置?多租户架构如何影响SaaS环境的整体体验?在此,我们将深入探讨租户架构如何塑造SaaS环境中的身份验证、授权以及多租户的整体影响。
在多租户身份管理中,您必须超越将身份单纯视为用户认证工具的思维。您需要扩展对身份的理解,纳入这样一个概念:每个经过认证的用户必须始终在特定租户的上下文中进行认证。虽然用户确实与这一体验相关联,但多租户架构的底层实现主要聚焦于与该用户关联的租户。因此,我们的身份模型必须扩展以涵盖用户和租户两者。基本目标是建立用户与租户之间更紧密的绑定关系,使其能够作为一个整体被访问、共享和管理。
在图4-9中,您将看到一个概念视图,展示了SaaS身份的组成结构。左侧是我标记为“用户身份”的经典视图。该身份主要用于描述和捕获个体的属性。姓名、电话号码、电子邮件地址等都是用于描述用户的典型属性。然而,在右侧,我还引入了“租户身份”的概念。租户更是一个实体而非个人。例如,一家公司作为租户订阅您的SaaS服务,而该租户通常拥有多个用户。
图4-9
在多租户环境中,这两个不同的身份概念被结合在一起,形成我所说的SaaS身份。SaaS身份必须以一种方式引入,使其能够成为系统各层级中传递的顶级身份构造。它成为将租户上下文传递给系统中所有需要访问用户和租户属性的组件的载体,SaaS身份直接映射到租户上下文。
关键在于,引入此SaaS身份时,不得以任何方式影响或复杂化传统的身份验证体验。您的SaaS身份验证体验必须保留遵循经典身份验证流程的自由,同时仍能实现用户身份与租户身份的合并。图4-10展示了此概念的实际应用。
图4-10
在该流程中,租户用户尝试访问SaaS网页应用(步骤1)。应用检测到用户未认证,并将其重定向至同时了解用户和租户身份的身份提供商(步骤2)。当用户认证成功后,身份提供商将负责返回SaaS身份(步骤3)。随后,该SaaS身份被传递给系统中其他所有组件(步骤4)。该身份包含支持SaaS应用程序剩余组件所需的租户和用户的所有属性。
虽然此流程可能因您的身份技术类型而有所不同,但这种体验的核心原则应在不同身份模型中保持一致。此外,流程还可能受租户如何进入您的系统并被路由到身份提供商的影响。例如,子域名、电子邮件地址或查找表可能影响您如何解析租户到对应身份提供商的路径。最终,您的目标是在流程前端解析并创建此SaaS身份,避免将此责任推入设计和实现的细节中。
绑定一个租户身份
在此阶段,我已讨论了用户身份与租户身份的关联。尽管这在概念上可行,但我们尚未探讨如何将这两个概念整合为真正的SaaS身份构造。显然,具体实现方式会因身份提供商而异。
为了便于讨论,本文将重点介绍如何利用开放授权(OAuth)和OpenID 连接(OIDC)规范来创建和配置SaaS身份。这些规范被众多现代身份提供商广泛采用,作为去中心化身份验证与授权的开放标准。因此,您会发现本文中介绍的技术与应用程序的身份模型之间存在一定的映射关系。
要将租户与用户关联,我们首先需要理解OIDC规范如何封装和传递用户的身份验证信息。通常对符合OIDC规范的身份提供商进行身份验证时,每次身份验证都会返回身份令牌和访问令牌。这些令牌以JSON Web Token(JWT)格式存储,包含用于下游授权的所有身份验证上下文。身份令牌用于传达用户信息,而访问令牌则用于授权用户访问不同资源。
在这些JWT中,您会发现一组属性和值,这些信息提供了关于用户的更详细描述。这些数据被称为声明(claims)。每个令牌中都包含一组默认声明,以确保常见属性的标准化表示。正是这些JWT成为了我们多租户身份模型中的通用凭证。
JWT的优势在于支持自定义声明。这些自定义声明本质上相当于用户定义的字段,可用于将您自己的属性/值对附加到JWT中。这为您提供了将租户上下文数据附加到这些令牌的机会。图4-11展示了如何将这些自定义租户声明添加到JWT中。
左侧是一个示例JWT,其中包含OIDC规范中定义的示例声明。我不会逐一解释所有内容,但值得特别指出的是,这里出现的特定用户属性。您会发现name、given_name、family_name、gender、birthdate和email均在此列表中。然而,右侧显示的是需要合并到JWT中的租户属性,这些属性以属性/值对的形式添加到标准化表示中。
图4-11
虽然这个模型并没有什么神奇或优雅之处,但能够将这些自定义声明作为第一类公民引入,带来了显著的优势。想象一下,将这些属性作为声明嵌入其中,最终如何塑造你的多租户身份验证和授权体验。图4-12突出了这个看似简单的构造如何在多租户架构中产生连锁影响。
图4-12
在此示例中,流程始于Web应用程序。用户访问该页面时未经过身份验证,系统将其重定向至身份提供商进行身份验证(步骤1)。这代表了一个非常熟悉且标准的流程,您可能已多次构建过。不同之处在于,身份验证体验返回的数据。在此处进行身份验证时,身份提供者将返回其标准令牌(步骤2)。然而,由于我已将身份提供者配置为使用租户特定的自定义声明,返回的令牌现在与之前讨论的SaaS身份相匹配。这些令牌的行为与其他令牌相同,但额外包含了创建SaaS身份所需的租户上下文信息。
现在,这些令牌可以作为承载令牌注入并发送到后端服务,继承OIDC和OAuth规范中内置的所有安全、生命周期和其他机制(步骤3)。这种策略在考虑其对后端服务整体体验的影响时尤为强大。图4-13展示了这些令牌如何在应用程序的不同多租户微服务之间流转的示例。
图4-13
我有三个不同的微服务,每个微服务都需要访问租户上下文。当您进行身份验证并收到一个令牌时,该令牌会被传递给您最初调用的微服务。在此示例中,调用进入订单微服务(步骤1)。然后,假设该服务需要调用另一个后端服务(Product)来完成其任务。它可以将同一令牌传递给Product服务(步骤2)。这种模式可以继续通过后续的下游服务调用级联(步骤3)。在此示例中,我假设可以将JWT作为承载令牌插入HTTP请求中。然而,即使您使用其他协议,也可能存在将JWT作为上下文的一部分注入的方法。
您可以想象,这样一个非常简单的机制最终会对您的整体多租户架构产生深远影响。这个单一的JWT将涉及您多租户环境实现中的众多关键组件。微服务将使用它进行日志记录、指标监控、计费、租户隔离、数据分区以及其他众多领域。更广泛的SaaS架构将利用它进行分层、限流、路由以及其他需要租户上下文的全局机制。因此,虽然这是一个简单的概念,但它在SaaS架构中的作用绝不能被低估。
在入驻期间填充自定义声明
我们已经了解了自定义声明如何将用户与租户关联。但可能不太清楚的是,这些声明实际上是如何以及何时被引入的。添加和填充这些自定义声明涉及两个相对简单的步骤。首先,在任何租户进行注册之前,通常需要配置身份提供商,并识别希望添加到身份验证体验中的每个自定义属性。在此,您将定义希望最终出现在自定义声明中的每个属性及其类型。这为身份提供商做好准备,使其能够接受新租户并允许其存储和配置包含这些额外属性的租户。
为新创建的租户填充自定义声明。在添加用户信息(如姓名、邮箱等)时,还需要为该用户填充租户上下文字段(租户ID、角色、层级)。这些数据必须在身份提供商中填充,因此即使入驻流程已完成,后续添加用户时仍需填充这些自定义属性。
明智地使用自定义声明
自定义声明是将租户上下文附加到令牌的有用机制。在某些情况下,团队可能会过度依赖此机制并扩展其作用,将其用于捕获和传递应用程序安全上下文。虽然这里没有硬性规定,但我通常假设如果某个字段是自定义声明,它在塑造租户上下文和影响全局授权流程中扮演着关键角色。
许多应用程序依赖于访问控制机制来启用或禁用对特定应用程序功能的访问。这些控制应在身份提供商的范围之外进行管理。通常,我认为在令牌中添加自定义声明来膨胀令牌。相反,此类控制应通过专为此目的设计的语言或技术栈机制实现。
有时可能难以确定某个属性应归类为自定义声明还是应用程序访问控制模型的一部分。在我看来,这通常可根据属性的生命周期和角色来决定。如果属性随应用程序的功能或能力的引入而不断演进,则应通过应用程序访问控制进行管理。一般而言,落入自定义声明中的属性在应用程序变更时不太可能发生变化。例如,令牌的内容不应因新增应用程序功能或配置选项而每周发生变化。
非集中化服务用于解决租户上下文
部分团队试图在租户身份与用户身份之间划定更严格的界限。在此类环境中,身份提供商仅用于验证用户身份。当用户通过验证后,该过程返回的令牌中不包含任何租户上下文信息。在此模型下,这些系统必须依赖下游机制来解析租户身份。图4-14展示了此实现方式的示例。
图4-14
在此示例中,Web应用程序对一个不了解租户上下文的身份提供商进行身份验证(步骤1)。成功的身份验证仍会返回我们之前讨论的JWT。然而,这些令牌不包含任何之前提到的租户特定自定义声明(步骤2)。这里唯一包含的数据是用户数据。该令牌随后被传递给订单微服务(步骤3)。现在,当订单服务需要访问特定租户的数据时,它需要确定当前请求关联的租户。由于JWT不包含此信息,您的代码需要从另一个服务获取上下文(步骤4)。在此示例中,我引入了一个租户映射服务,该服务接受JWT,提取用户信息,将用户映射到租户,并返回租户标识符(步骤5)。此标识符随后用于获取该特定租户的订单(步骤6)。
表面上看,这似乎是一个完全合理的策略。然而,它实际上给许多SaaS环境带来了真正的挑战。较小的问题是,它在用户和租户之间创建了硬性分离,要求团队独立管理用户和租户的耦合状态。更大的问题是,系统中的每个服务都必须通过这个集中式映射机制来解析租户上下文。想象一下,这个步骤在数百个服务和数千个请求中重复执行。采用这种方法的团队很快会发现,租户映射服务最终会在系统中形成一个显著的瓶颈,迫使团队试图优化一个实际上不提供任何业务价值的服务。
这也是为什么用户和租户上下文必须绑定在一起,并在整个多租户架构中普遍共享的另一个原因。作为一个经验法则,我的目标是确保没有任何服务需要调用外部机制来解析和获取租户上下文。你希望通过包含SaaS身份信息的JWT,共享关于租户的所有必要信息。当然,可能存在例外情况,但这应是你在考虑如何映射用户到租户时应秉持的总体思路。
联合SaaS身份
到目前为止,我所描述的大部分内容假设您的SaaS系统能够与您控制的单一身份提供商配合运行。虽然这代表了理想场景并最大化了您的选择,但假设每个SaaS解决方案都采用此模型并不现实。部分SaaS提供商因业务、领域或客户需求,需要支持客户或第三方托管的身份提供商。
我常见的一种情况是,SaaS客户对现有内部身份提供商存在企业级依赖。部分客户可能在购买条件中要求SaaS提供商支持从这些内部身份提供商进行身份验证。这类场景通常需要权衡获取该客户的价值与为环境增加复杂性所带来的影响可能影响整体SaaS体验的敏捷性和运营效率。然而,当合适的机会出现时,业务参数可能会推动团队采用允许支持此模型的策略。
通常,这是通过增加租户配置级别来实现的,即在租户入驻过程中添加对外部托管身份提供商的额外配置支持。目标是使这一过程尽可能无缝,尽量避免引入任何侵入性或一次性代码,这些代码可能包含租户特定的自定义设置。另一个挑战是,在某些情况下,您需要同时支持外部和内部身份提供商。现实情况是,大多数客户很可能期望您的解决方案包含内置的身份支持。图4-15展示了此身份模式的各个组成部分。
图4-15
在此示例的中心位置,您会看到一个身份验证管理器。这是一个概念上的占位符,用于在身份验证流程中引入能够支持更分布式身份提供商集的服务。要使这套机制正常工作,您的系统必须始终确定身份提供商的托管方式。每次用户需要身份验证时,您都需要检查该用户并检索其身份配置,其中包含描述特定租户位置和配置的数据。
在图4-15的左侧,我包含了需要由单一SaaS体验支持的内部和外部托管身份提供商的混合。其中租户使用自己的身份提供商。其余租户使用您内部托管的身份提供商。
该模型看似相当直观。然而,关键在于您的系统无法控制这些外部身份提供商。因此,您无法配置这些提供商的声明,也无法让您的注册流程向这些提供商管理的身份数据中添加额外的租户上下文。这意味着从身份验证请求中返回的JWT将不包含多租户环境中至关重要的租户上下文。为了解决这个问题,您的解决方案需要引入新功能,能够丰富从这些外部身份提供商返回的令牌,并承担起将这些令牌与您SaaS环境中管理的租户上下文进行关联的责任。这使得所有下游服务仍可继续依赖租户感知型JWT令牌,无论用户通过哪个身份提供商进行认证。令牌的丰富方式将取决于解决方案的具体实现。存在一些策略可提供钩子,允许您动态注入额外租户上下文。在其他情况下,您可能需要更定制化的解决方案。不过,身份验证领域的联合模型通常会提供多种技术来处理此用例。
我包含此模型是因为它代表了实际环境中不可避免的模式。值得注意的是,此方法存在明显缺点。每次您需要插入身份验证流程时,都会在多租户架构的安全足迹中承担额外角色。您可能还需要解决因参与身份验证流程而带来的可扩展性和单点故障要求。因此,尽管这可能是必要的,但它会带来实际的负担,您需要仔细考虑。
租户分组/映射构造
虽然身份提供商通常遵循已建立的规范(如OIDC、OAuth2),但用于组织和管理身份的构造可能因身份提供商而异。这些提供商提供了多种不同的构造来分组和组织用户。这在多租户环境中尤为重要,因为您可能希望将属于同一租户的所有用户分组在一起。这些分组构造可能会影响您在身份提供商中部署租户的方式。在某些情况下,您还可以利用这些分组来对租户应用分层策略,以塑造其身份验证和授权体验。
以Amazon Cognito为例,您会发现它提供了多种组织租户的方式。Cognito引入了“用户池”的概念。这些用户池用于存储一组用户,并且可以单独配置,允许提供独立的身份验证体验。这可能导致“每个租户一个用户池”的模型,即每个租户拥有自己的用户池。另一种选择是将所有租户放入单个用户池,并通过其他机制(如组)将用户与租户关联。您还需考虑身份提供商的任何限制如何影响策略选择。
在选择这些不同的身份构造时,您需要权衡一些取舍。例如,您拥有的租户数量可能使得为每个租户单独设置用户池变得不切实际。或者,您可能不需要在租户之间进行太多区分,更倾向于将所有租户统一配置和管理。您可能还在考虑这些选择如何影响SaaS解决方案的身份验证流程。如果为每个租户设置独立的用户池,您需要在身份验证过程中考虑如何将租户映射到其对应的用户池。这可能会引入一层额外的间接性,而您可能不希望将其纳入解决方案中。
规模、身份要求以及其他诸多因素将决定您如何将租户映射到身份提供商支持的构造。关键在于,在制定SaaS身份策略时,您需要识别可用于分组租户的不同组织单元,并确定这些单元将如何影响多租户身份验证的规模、身份验证流程和配置。
不同的组织结构也带来不同的身份配置选项。身份提供商通常提供多种可用于配置身份验证体验的选项。例如,多因素身份验证(MFA)作为身份功能提供,可启用或禁用。您还可以配置密码格式要求和过期策略。
这些不同配置选项的设置无需全局应用于所有租户。您可能希望向不同租户层级提供不同的身份功能。例如,您可能仅向高级租户层级提供MFA,或决定在SaaS应用程序的租户管理界面中展示这些配置选项,并允许每个租户自行配置这些身份设置。这可以成为一项差异化功能,为租户增添价值,并使他们能够创建最符合业务需求的身份体验。
您是否提供此身份自定义功能,将取决于您的身份提供商如何组织和呈现这些选项。部分提供商允许您为单个租户单独配置这些选项,而其他提供商仅允许全局配置。您需要深入研究特定身份提供商的架构,以确定是否可以将这些身份策略与单个租户关联。
跨租户共享用户ID
您SaaS系统中的每位用户都拥有一个用户ID,用于在租户中唯一标识该用户。此用户标识符通常以电子邮件地址的形式呈现。在多数情况下,单个用户仅与单个SaaS租户相关联。然而,SaaS提供商有时需要将单个电子邮件地址与多个租户建立关联,这当然会为您的身份验证流程增加复杂性。在登录流程的某个环节,您的SaaS系统需要确定您正在访问的租户。
虽然我曾看到过支持此模式的需求,但尚未发现任何现成的策略来处理此用例。不过,我确实看到过一些在此场景下应用的模式。我见过最粗暴的方法是将租户解析推给最终用户:在登录过程中,系统会检测用户属于多个租户,并提示用户选择目标租户。这种方法远非优雅,且会导致信息泄露——任何人都可通过邮箱地址查看你所属的租户(仅当你属于多个租户时)。在该模型中,你需要维护一个映射表将用户与租户关联,并在认证流程开始前进行预先查询。
更优雅的解决方案是依赖一个能更明确提供上下文的认证体验,最佳示例可能是域名和子域名。若每个租户被分配一个子域名(如tenant1.saasprovider.com),认证流程可通过该子域名获取租户上下文。随后系统将针对指定租户进行认证。此方案可让用户在无需额外步骤识别目标租户的情况下完成认证。在此场景中还存在其他复杂情况,例如,假设所有用户均在共享身份提供商架构中运行。
在此模式下,身份提供商将要求每个用户必须唯一。这将导致无法支持单个用户ID与多个租户关联。此时,您可能需要考虑采用更细粒度的架构来存储每个租户的数据(如之前提到的用户池)。
租户认证不等于租户隔离
在探讨身份验证与JWT的过程中,我时常发现团队会将身份验证等同于租户隔离。这种观点的假设是:身份验证是租户进入的门槛,一旦通过该验证,便满足了多租户环境中租户隔离的要求。
这确实存在脱节现象。没错,身份验证通过发放包含租户上下文的JWT启动了隔离机制。然而,微服务中的代码仍可能包含这样的实现:即便代表已验证用户操作,仍能访问其他租户的资源。租户隔离机制基于从已验证用户获取的租户上下文构建,通过实施完全独立的控制层与防护措施,确保代码不得跨越租户边界。第九章将深入探讨这些策略。