你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

使用多租户应用程序实现跨租户通信

本指南提供了一种解决方案,用于实现不同 Microsoft Entra 租户管理的不同 Azure 订阅中托管的服务之间的双向安全通信。

由于许多服务固有的限制,在 Azure 中保护多租户通信可能会具有挑战性。 可以使用 Azure 托管标识从 Microsoft Entra ID 获取令牌,从而无需直接管理凭据。 但是,Azure 托管标识不能跨租户边界工作;典型的替代方法是使用共享机密,例如共享访问签名 URL。 请记住,如果使用共享机密,则需要跨 Microsoft Entra 租户边界安全地分发和轮换机密。

避免此开销的一个选项是创建多租户应用程序来表示工作负载的标识。 通过同意过程,你可以让外部租户知道此工作负载标识,并最终允许其对外部租户中的服务进行身份验证。

本文给出了使用示例代码的该模式的示例实现。

此模式可用于任何多租户方案,这些方案具有需要跨 Microsoft Entra 租户边界进行通信的各种服务。

体系结构

跨租户通信体系结构的示意图。

下载此体系结构的 PowerPoint 文件

工作流

以下工作流与上图相对应:

  1. 提供程序端的管理员创建一个多租户应用程序注册,并为其设置一个客户端机密。

  2. 客户端的管理员在其租户中预配服务主体。 此服务主体基于提供程序创建的多租户应用程序。 可以通过多种方法执行此步骤。 在本例中,我们选择创建 URL 以提供给客户租户管理员;但你也可以使用 Microsoft Graph API。

  3. 客户将此基于角色的访问控制 (RBAC) 角色应用于此新服务主体,以便获得访问 Azure 服务总线的授权。

  4. 提供程序的函数应用使用应用程序注册的客户端 ID 和客户端机密将经过身份验证的消息发送到客户的服务总线队列。

  5. 客户的函数应用使用托管标识通过服务总线触发器从队列中读取提供程序的消息。

  6. 收到消息后,客户的函数应用通常会在将状态消息发送回提供程序之前执行一些工作。 在这种情况下,出于演示目的,函数应用会立即向同一服务总线中单独队列上的提供程序发送一条状态消息。

  7. 此函数应用通过 Azure Functions 触发的计时器从客户租户的状态队列中读取。

方案详细信息

一个提供程序有多个客户。 提供程序和每个客户都有自己单独的 Microsoft Entra ID 租户和 Azure 资源。 提供程序和每个客户都需要一种安全的双向通信方法,以便他们可以通过服务总线队列交换消息。 解决方案应有引人注目的标识故事,避免引入不必要的凭据或机密。

关于多租户应用程序需要了解哪些知识

  • 应用程序对象是应用程序的全局唯一实例。

  • 当应用程序在 Microsoft Entra 中注册时,会在租户中自动创建应用程序对象和服务主体对象。

  • 在使用应用程序并引用应用程序对象的每个租户中都会创建一个服务主体对象。 应用程序对象与其对应的服务主体对象之间存在一对多关系。

  • 应用程序对象是应用程序的全局表示形式,可以跨所有租户使用。 服务主体对象是在特定租户中使用的本地表示。

  • 必须在将使用应用的每个租户中创建服务主体对象,以便建立一个标识来访问租户保护的资源。 单租户应用程序在其租户中只有一个服务主体对象。 此服务主体对象被创建,并允许在应用程序注册期间使用。 多租户应用程序还会在租户中的某个用户已同意使用它的每个租户中创建服务主体对象。

  • 若要访问由 Microsoft Entra 租户保护的资源,安全主体必须代表需要访问的实体。

  • 当应用程序被授予了对租户中资源的访问权限时(根据注册或许可),将创建一个服务主体对象。 此体系结构通过同意流实现。

提供程序如何向客户发送消息?

理想情况下,提供程序能够将托管标识分配给负责将消息发送到客户的队列的 Azure 计算资源。 客户的租户被配置为信任来自提供程序租户的托管标识。 然而,在两个 Microsoft Entra 租户之间实现真正的联合,从本质上允许从一个租户到另一个租户“共享”标识,目前还不可能。 因此,提供程序需要使用客户识别的标识进行身份验证。 提供程序需要作为客户知道的服务主体对客户的 Microsoft Entra 租户进行身份验证。

我们建议提供程序在其自己的租户中注册多租户应用程序,然后让每个客户在其租户中预配关联的服务主体。 然后,提供程序可以使用此服务主体向客户的租户和客户托管的 API 进行身份验证。 在这种方法中,提供程序永远不需要共享客户端机密。 凭据管理由提供程序全权负责。

客户如何向提供程序发送信息?

我们建议客户创建或托管一个队列,提供程序可以从中读取。 客户将消息写入队列。 提供程序使用服务主体对象重复轮询每个客户队列中的消息。 这种方法的缺点是,当提供程序接收到消息时,会引入轮询延迟。 代码还需要在提供程序中更频繁地运行,因为它必须唤醒并执行轮询逻辑,而不是等待事件触发它。 但是,凭据管理由提供程序全权负责,这增强了安全性。

另一种可能的解决方案是让提供程序为每个客户创建或托管队列。 每个客户都创建自己的多租户应用程序,并请求提供程序在其租户中将其预配为服务主体对象。 然后,客户使用此服务主体对象将消息发送到提供程序端的特定于客户的队列。 凭据管理仍然由提供程序全权负责。 此方法的一个缺点是,提供程序必须将与客户应用程序关联的服务主体预配到其租户中。 此为手动过程,提供程序可能不希望手动步骤成为加入新客户的流程的一部分。

示例代码设置

以下步骤指导你完成在提供程序和客户之间设置跨租户通信的过程。

提供程序安装程序

提供程序设置包括生成和预配多租户应用程序服务主体的步骤以及预配客户租户的步骤。

  1. 创建 HTTP 触发的函数应用,向客户租户内的客户服务总线命令队列发送要写入的消息。

  2. 创建时间触发的函数应用,定期检查客户租户内客户服务总线内的状态队列。

在提供程序的租户中创建多租户应用程序

首先,在提供程序的租户中创建多租户应用程序,并在客户的租户中预配该标识。 在此方案中,标识是服务主体。 本文前面的体系结构介绍了如何将提供程序租户中的服务主体设置和预配到客户的租户中。 该体系结构还概述了如何使用多个 Microsoft Entra 租户进行预配。

  1. 选择多租户组织选项。

  2. 将以下网站添加为重定向 URI:https://entra.microsoft.com。 可以更改此 URI 以满足业务需求。

  3. 注册并记下应用程序(客户端)ID 值。

创建新客户端机密

  1. 创建多租户应用程序后,请为此服务主体创建客户端机密。

  2. 将生成的机密保存到安全位置。 机密和客户端 ID 是在授权代码流中交换代码以及在下一步中交换 ID 令牌所需的客户端凭据。

Azure Functions - HTTP 触发

使用 HTTP 函数,通过向客户的服务总线部署队列发送消息,从提供程序的租户启动部署。 我们选择了 HTTP 触发的函数作为传递方法,以启动此概念证明。 之前生成的服务主体充当访问客户租户并写入服务总线中特定队列的凭据。 还需要完成客户设置,才能使此步骤正常工作。

Azure Functions - 计时器触发

使用计时器触发的函数从客户的租户中轮询部署状态队列。 在这个概念验证中,我们每 10 秒轮询一次部署状态队列以进行演示。 这种方法使客户不需要使用服务主体来访问提供程序的租户。

客户设置

  1. 通过修改和使用提供的 URL 来预配服务主体。

  2. 确定提供程序服务主体的范围,以使用适当的 RBAC 控件。

  3. 创建服务总线触发的函数,以从服务总线消息队列中读取消息,并将消息放入另一个队列。 为了便于演示,此流最适合用于测试功能。

  4. 为服务总线触发的函数创建系统分配的托管标识。

  5. 分配系统分配的托管标识服务总线作用域。

将服务主体从提供程序的租户预配到客户的租户

  1. client_id 查询字符串参数替换为自己的客户端 ID 后,请访问以下 URL:https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize?response_type=code&response_mode=query&scope=openid&client_id=<your_client_ID>

    还可以使用管理员 Microsoft Graph API 调用、Azure PowerShell 命令或 Azure CLI 命令将服务主体预配到另一个 Microsoft Entra 租户。

  2. 使用客户租户的帐户登录。

  3. 在同意屏幕上,选择接受,以在客户租户中预配提供程序的应用程序。 URL 最终会重定向到 microsoft.com,这仍具有将标识预配到客户租户所需的效果。

  4. 转到企业应用程序查看新预配的服务主体,验证客户的 Microsoft Entra 租户中的标识。

为预配的服务主体设置 RBAC

将提供程序服务主体从提供程序服务主体设置的范围限定为在服务总线上具有“服务总线数据所有者”角色。 此服务主体既用于向具有 HTTP 触发函数的队列写入,也用于从计时器触发函数中读取队列。 请确保将“Azure 服务总线数据所有者”角色添加到服务主体。

Azure Functions - 服务总线触发器

按照基于标识的函数教程中的步骤,从服务总线队列定义函数触发器,并了解如何设置托管标识。 本指南可帮助在将消息添加到队列时从服务总线队列触发函数应用。 将消息放入其他队列时,也可以使用托管标识。 出于演示目的,我们使用同一函数推送消息。

在新创建的服务总线命名空间中,选择访问控制 (IAM)。 可以在控制平面中查看和配置谁有权访问资源。

使用托管标识向函数应用授予对服务总线命名空间的访问权限。

  1. 请确保将“Azure 服务总线数据接收方”角色添加到托管标识。

  2. 在“托管标识”选择器中,从系统分配的托管标识类别中选择函数应用。 标签函数应用旁边的括号中可能有一个数字。 该数字表示订阅中有多少具有系统分配标识的应用。

在函数应用中连接到服务总线

  1. 在门户中,搜索函数应用,或在函数应用页上转到该应用。

  2. 应用程序设置中,选择 + 新建,以创建表中的新应用程序设置。 Service BusConnection__fullyQualifiedNamespace <SERVICE_BUS_NAMESPACE>.Service Bus.windows.net

服务主体客户端机密生命周期管理

如果将机密引入跨租户体系结构,则需要管理这些生成的客户端机密的生命周期。 请参阅机密管理的最佳做法,了解如何安全地存储、轮换和监控客户端机密。

本地设置

每个子目录都包含 local.settings.json 文件的存根版本,可对其进行修改以在本地运行 Azure 函数。 若要在 Azure 中配置设置,请更新应用程序设置

DefaultAzureCredential 命令在到达 Azure CLI 凭据之前枚举多个设置。 为了避免混淆,我们建议在开发本地函数时运行 az login -t <tenant ID> 命令来选择正确的凭据。

作者

本文由 Microsoft 维护, 它最初是由以下贡献者撰写的。

主要作者:

后续步骤