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

向机器人添加单一登录

适用于:SDK v4

本文介绍如何在机器人中使用单一登录 (SSO) 功能。 为此,此功能使用“使用者”机器人(也称为“根”机器人或“父”机器人)与“技能”或“子”机器人进行交互。 本文使用“根机器人”和“技能机器人”术语。

如果包括 SSO 支持,则用户可以使用标识提供者登录到根机器人,在控制传递至技能时无需再次登录。

根机器人和技能机器人是单独的机器人,在可能不同的服务器上运行,每个都有其自身不同的内存和状态。 有关技能的详细信息,请参阅技能概述实施技能。 有关用户身份验证的详细信息,请参阅 Bot Framework 身份验证基础知识用户身份验证向机器人添加身份验证

重要

将 Azure 机器人服务身份验证与 Web 聊天配合使用时,必须牢记一些重要的安全注意事项。 有关详细信息,请参阅 REST 身份验证文章中的安全注意事项部分。

先决条件

关于本示例

本文引用了两个机器人:RootBotSkillBot。 RootBot 将活动转发到 SkillBot 。 它们对此典型的技能方案建模:

  • 根机器人调用一个或多个技能机器人。
  • 根机器人和技能机器人都实现向机器人添加身份验证一文中所述的基本身份验证。
  • 用户登录到根机器人。
  • 由于 SSO 且已登录到根机器人,因此无需再次进行用户交互即可登录到技能机器人。

有关 Bot Framework 如何处理身份验证的概述,请参阅用户身份验证。 有关 SSO 背景信息,请参阅单一登录

RootBot 支持 SSO。 它代表用户与 SkillBot 通信,而无需用户再次对 _SkillBot 进行身份验证。

对于本示例中的每个项目,需要以下各项:

  1. 要在 Azure 中注册机器人资源的 Microsoft Entra ID 应用程序。
  2. 用于身份验证的 Microsoft Entra ID 标识提供者应用程序。

    注意

    目前仅支持 Microsoft Entra ID 标识提供者。

创建 Azure RootBot 资源

  1. Azure 门户中为 RootBot 创建 Azure 机器人资源。 按照创建 Azure 机器人资源中所述的步骤进行操作。
  2. 复制并保存机器人注册应用 ID 和客户端密码 。

为 RootBot 创建 Microsoft Entra ID 标识

Microsoft Entra ID 是一个云标识服务,你可以通过它生成应用程序,以便使用 OAuth2.0 之类的行业标准协议安全地登录用户。

  1. 为使用 Microsoft Entra ID 对用户进行身份验证的 RootBot 创建标识应用程序。 按照创建 Microsoft Entra ID 标识提供者中所述的步骤进行操作。

  2. 选择左侧窗格中的“清单”。

  3. accessTokenAcceptedVersion 设置为 2。

  4. 选择“保存”。

  5. 选择左侧窗格中的“公开 API”。

  6. 选择右侧窗格中的“添加范围”

  7. 在最右侧的“添加范围”部分中,选择“保存并继续”。

  8. 在显示的窗口中,在“谁能同意?”下选择“管理员和用户”。

  9. 输入剩余的必填信息。

  10. 选择“添加作用域”。

  11. 复制并保存范围值。

为 RootBot 创建 OAuth 连接设置

  1. RootBot 机器人注册中创建 Microsoft Entra ID 连接,并输入 Microsoft Entra ID 中所述的值以及下面所述的值。

  2. 将“令牌交换 URL”留空。

  3. 在“范围”框中输入前面步骤中保存的 RootBot 范围值。

    注意

    “范围”包含用户最初登录到根机器人的 URL,而“令牌交换 URL”留空。

    例如,假设根机器人 appidrootAppId,技能机器人 appidskillAppId。 根机器人的“范围”类似于 api://rootAppId/customScope,用于登录用户。 然后,此根机器人的“范围”将在 SSO 期间与 api://skillAppId/customscope 交换。

  4. 复制并保存连接的名称。

创建 Azure SkillBot 资源

  1. Azure 门户中为 SkillBot 创建 Azure 机器人资源。 按照创建 Azure 机器人资源中所述的步骤进行操作。
  2. 复制并保存机器人注册应用 ID 和客户端密码 。

为 SkillBot 创建 Microsoft Entra ID 标识

Microsoft Entra ID 是一个云标识服务,你可以通过它生成应用程序,以便使用 OAuth2.0 之类的行业标准协议安全地登录用户。

  1. 为使用 Microsoft Entra ID 对机器人进行身份验证的 SkillBot 创建标识应用程序。 按照创建 Microsoft Entra ID 标识提供者中所述的步骤进行操作。

  2. 选择左侧窗格中的“清单”。

  3. accessTokenAcceptedVersion 设置为 2。

  4. 选择“保存”。

  5. 选择左侧窗格中的“公开 API”。

  6. 选择右侧窗格中的“添加范围”

  7. 在最右侧的“添加范围”部分中,单击“保存并继续”。

  8. 在显示的窗口中,在“谁能同意?”下选择“管理员和用户”。

  9. 输入剩余的必填信息。

  10. 选择“添加作用域”。

  11. 复制并保存范围值。

  12. 选择添加客户端应用程序。 在最右侧部分的“客户端 ID”框中,输入之前保存的“RootBot 标识”应用 ID。 确保使用的是 RootBot 标识,而不是注册应用 ID。

    注意

    对于客户端应用程序,Azure AI 机器人服务不支持使用 Microsoft Entra ID B2C 标识提供者进行单一登录。

  13. 在“授权的范围”下,选中范围值对应的框。

  14. 选择添加应用程序

  15. 在左侧导航窗格中,选择“API 权限”。 最佳做法是为应用显式设置 API 权限。

    1. 在右窗格中,选择“添加权限”。

    2. 依次选择“Microsoft API”、“Microsoft Graph” 。

    3. 选择“委托的权限”,确保选中所需权限。 本示例需要下面列出的权限。

      注意

      标记为“需要管理员同意”的任何权限同时需要用户和租户管理员登录。

      • openid
      • profile
      • User.Read
      • User.ReadBasic.All
    4. 选择“添加权限”。

为 SkillBot 创建 OAuth 连接设置

  1. SkillBot 机器人注册中创建 Microsoft Entra ID 连接,并输入 Microsoft Entra ID 中所述的值以及下面所述的值。

  2. 在“令牌交换 URL”框中输入前面步骤中保存的 SkillBot 范围值。

  3. 在“作用域”框中,输入用空格分隔的以下值: profile User.Read User.ReadBasic.All openid

  4. 复制连接的名称并保存到文件中。

测试连接

  1. 选择连接项打开创建的连接。
  2. 选择“服务提供方连接设置”窗格顶部的“测试连接”。
  3. 第一次进行此操作时,应该会打开一个新的浏览器选项卡(其中列出了应用请求的权限)并提示你接受。
  4. 选择“接受”。
  5. 然后,应会重定向到“<连接名称 > 的连接测试成功”页。

有关详细信息,请参阅适用于开发人员的 Microsoft Entra ID (v1.0) 概述Microsoft 标识平台 (v2.0) 概述。 若要了解 v1 和 v2 终结点的差异,请参阅为什么要更新到 Microsoft 标识平台 (v2.0)?。 有关完整信息,请参阅 Microsoft 标识平台(旧称针对开发人员的 Microsoft Entra ID)

准备示例代码

必须按如下所述更新两个示例中的 appsettings.json 文件。

  1. 从 GitHub 存储库克隆具有简单技能使用者和技能的 SSO 示例。

  2. 打开 SkillBot 项目 appsettings.json 文件。 从保存的文件中,分配以下值:

    {
        "MicrosoftAppId": "<SkillBot registration app ID>",
        "MicrosoftAppPassword": "<SkillBot registration password>",
        "ConnectionName": "<SkillBot connection name>",
        "AllowedCallers": [ "<RootBot registration app ID>" ]
    }
    
    
  3. 打开 RootBot 项目 appsettings.json 文件。 从保存的文件中,分配以下值:

    {
        "MicrosoftAppId": "<RootBot registration app ID>",
        "MicrosoftAppPassword": "<RootBot registration password>",
        "ConnectionName": "<RootBot connection name>",
        "SkillHostEndpoint": "http://localhost:3978/api/skills/",
        "BotFrameworkSkills": [
                {
                "Id": "SkillBot",
                "AppId": "<SkillBot registration app ID>",
                "SkillEndpoint": "http://localhost:39783/api/messages"
                }
            ]
    }
    

测试示例

使用以下各项进行测试:

  • RootBot 命令

    • login 允许用户使用 RootBot 登录到 Microsoft Entra ID 注册。 登录后,SSO 还会负责登录到 SkillBot。 用户无需再次登录。
    • token 显示用户的令牌。
    • logoutRootBot 中注销用户。
  • SkillBot 命令

    • skill login 允许 RootBot 代表用户登录到 SkillBot。 如果用户已登录,则不会向其显示登录卡,除非 SSO 失败。
    • skill token 显示来自 SkillBot 的用户令牌。
    • skill logoutSkillBot 中注销用户

注意

用户首次尝试使用技能上的 SSO 时,可能会向其显示用于登录的 OAuth 卡。 这是因为用户尚未同意技能的 Microsoft Entra ID 应用。 若要避免此问题,他们可以为 Microsoft Entra ID 应用请求的任何图形权限授予管理员同意。

安装 Bot Framework Emulator(如果尚未安装)。 另请参阅使用模拟器进行调试

需要为机器人示例登录配置 Emulator 才能正常工作。 使用以下步骤:如配置 Emulator 进行身份验证中所示。

配置身份验证机制后,你可以执行实际的机器人示例测试。

  1. 在 Visual Studio 中,打开 SSOWithSkills.sln 解决方案,并将其配置为开始使用多个进程进行调试

  2. 在计算机本地开始调试。 请注意,在 RootBot 项目 appsettings.json 文件中,你具有以下设置:

    "SkillHostEndpoint": "http://localhost:3978/api/skills/"
    "SkillEndpoint": "http://localhost:39783/api/messages"
    

    注意

    这些设置表示,同时在本地计算机上运行 RootBotSkillBot。 模拟器与端口 3978 上的 RootBot 通信,RootBot 与端口 39783 上的 SkillBot 通信。 一旦开始调试,便会打开两个默认浏览器窗口。 一个在端口 3978 上,另一个在端口 39783 上。

  3. 启动模拟器。

  4. 在连接到机器人时,输入 RootBot 注册应用 ID 和密码。

  5. 键入 hi 以启动对话。

  6. 输入“login”。 RootBot 将显示“登录到 AAD”身份验证卡。

    登录卡的示例。

  7. 选择“登录”。 随即显示弹出对话框“确认打开 URL”。

    “打开 URL”确认消息的屏幕截图。

  8. 选择“确认”。 随即登录,并显示 RootBot 令牌。

  9. 输入“token”以再次显示该令牌。

    显示根令牌的消息示例。

    现在,你已准备好与 SkillBot 进行通信。 使用 RootBot 登录后,在注销前都无需再次提供凭据。这表明 SSO 正常工作。

  10. 在模拟器框中输入“skill login”。 系统不会要求你再次登录。 而是显示 SkillBot 令牌。

  11. 输入“技能令牌”以再次显示该令牌。

  12. 现在,你可以输入“skill logout”以从 SkillBot 注销。 然后输入“logout”以从 SimpleRootBoot 注销。

其他信息

以下时间序列图适用于本文中使用的示例,并说明了所涉及的各种组件之间的交互。 ABS 代表“Azure AI 机器人服务”

说明技能令牌流的序列图。

  1. 用户首次输入 RootBot 的 login 命令。
  2. RootBot 发送要求用户登录的 OAuthCard 。
  3. 用户输入发送到 ABS(Azure AI 机器人服务)的身份验证凭证。
  4. ABS 将基于用户凭据生成的身份验证令牌发送到 RootBot 。
  5. RootBot 显示供用户查看的根令牌。
  6. 用户输入 SkillBot 的 skill login 命令。
  7. SkillBot 将 OAuthCard 发送到 RootBot 。
  8. RootBot 请求提供 ABS 中的可交换令牌 。
  9. SSO 将 SkillBot 技能令牌发送到 RootBot
  10. RootBot 显示供用户查看的技能令牌。 请注意,无需用户登录到 SKillBot 即生成了技能令牌。 其原因在于 SSO。

以下示例演示如何进行令牌交换。 代码来自 TokenExchangeSkillHandler.cs 文件。

private async Task<bool> InterceptOAuthCards(ClaimsIdentity claimsIdentity, Activity activity)
{
    var oauthCardAttachment = activity.Attachments?.FirstOrDefault(a => a?.ContentType == OAuthCard.ContentType);
    if (oauthCardAttachment != null)
    {
        var targetSkill = GetCallingSkill(claimsIdentity);
        if (targetSkill != null)
        {
            var oauthCard = ((JObject)oauthCardAttachment.Content).ToObject<OAuthCard>();

            if (!string.IsNullOrWhiteSpace(oauthCard?.TokenExchangeResource?.Uri))
            {
                using (var context = new TurnContext(_adapter, activity))
                {
                    context.TurnState.Add<IIdentity>("BotIdentity", claimsIdentity);

                    // AAD token exchange
                    try
                    {
                        var result = await _tokenExchangeProvider.ExchangeTokenAsync(
                            context,
                            _connectionName,
                            activity.Recipient.Id,
                            new TokenExchangeRequest() { Uri = oauthCard.TokenExchangeResource.Uri }).ConfigureAwait(false);

                        if (!string.IsNullOrEmpty(result?.Token))
                        {
                            // If token above is null, then SSO has failed and hence we return false.
                            // If not, send an invoke to the skill with the token. 
                            return await SendTokenExchangeInvokeToSkill(activity, oauthCard.TokenExchangeResource.Id, result.Token, oauthCard.ConnectionName, targetSkill, default).ConfigureAwait(false);
                        }
                    }
                    catch
                    {
                        // Show oauth card if token exchange fails.
                        return false;
                    }

                    return false;
                }
            }
        }
    }
    return false;
}