向 Teams 应用添加单一登录

屏幕截图显示了 Teams 工具包 v4 弃用说明。

Microsoft Teams 为应用提供单一登录 (SSO) 函数,以获取登录的 Teams 用户令牌以访问 Microsoft Graph 和其他 API。 Teams 工具包通过抽象化简单 API 后面的一些Microsoft Entra ID流和集成来促进交互。 这使你可以轻松地将 SSO 功能添加到 Teams 应用。

将 SSO 添加到适用于 Microsoft Visual Studio Code的 Teams 应用

对于在聊天、团队或频道中与用户交互的应用,SSO 以自适应卡片的形式显示,用户可以与之交互以调用Microsoft Entra同意流。

启用 SSO 支持

Teams 工具包可帮助你将 SSO 添加到 Visual Studio Code 中的以下 Teams 功能:

  • Tab
  • Bot
  • 通知机器人:restify 服务器
  • 命令机器人
  • 工作流机器人
  • 消息扩展

使用 Visual Studio Code 添加 SSO

可以执行以下步骤,在 Visual Studio Code 中使用 Teams 工具包添加 SSO:

  1. 打开 Visual Studio Code

  2. 从Visual Studio Code活动栏中选择“Teams 工具包”。

  3. 在“开发”部分选择“查看操作指南”。

    屏幕截图显示了“查看操作指南”选项的选择。

  4. 从下拉列表中,选择“ 在 Teams 中开发单个 Sign-On 体验”。 你将重定向到相应的操作指南。

    屏幕截图显示在Visual Studio Code中以红色突出显示的单一登录功能。

    开发 操作指南
    在 Teams 中开发单一登录体验 如何开发单一登录体验

注意

启用 SSO 后,默认情况下,Teams Toolkit 会预配单租户Microsoft Entra应用,这意味着只有 M365 帐户所在的同一目录中的用户和来宾帐户才能登录到 Teams 应用。 有关支持多租户更新 TeamsFx 项目的详细信息,请参阅对 Microsoft Entra 应用的多租户支持

另请参阅

屏幕截图显示了 Teams 工具包 v4 弃用说明。

Microsoft Teams 为应用提供单一登录 (SSO) 函数,以获取登录的 Teams 用户令牌以访问 Microsoft Graph 和其他 API。 Teams 工具包通过抽象化一些简单 API 背后的一些Microsoft Entra ID流和集成来促进交互。 这使你可以轻松地将 SSO 功能添加到 Teams 应用。

将 SSO 添加到适用于 Microsoft Visual Studio Code的 Teams 应用

对于在聊天、团队或频道中与用户交互的应用,SSO 以自适应卡片的形式显示,用户可以与之交互以调用Microsoft Entra同意流。

启用 SSO 支持

Teams 工具包可帮助你将 SSO 添加到 Visual Studio Code 中的以下 Teams 功能:

  • Tab
  • Bot
  • 通知机器人:restify 服务器
  • 命令机器人
  • 工作流机器人
  • 消息扩展

使用 Visual Studio Code 添加 SSO

可以执行以下步骤,在 Visual Studio Code 中使用 Teams 工具包添加 SSO:

  1. 打开 Visual Studio Code

  2. 从Visual Studio Code活动栏中选择“Teams 工具包”。

  3. 选择“开发”下的“添加功能”。

    屏幕截图显示Visual Studio Code“开发”选项下的“添加功能”选项。

  4. 可以选择“ 查看>命令面板...” 以查看 “添加功能 ”窗口。

  5. 选择“ 单一登录”。

    屏幕截图显示在Visual Studio Code中以红色突出显示的单一登录功能。

使用 TeamsFx CLI 添加 SSO

可以在项目根目录中运行teamsfx add sso命令。

注意

此功能为所有现有适用功能启用 SSO。 如果以后向项目添加功能,请按照相同的步骤启用 SSO。

使用 Teams 工具包自定义项目

下表列出了 Teams 工具包对项目所做的更改:

类型 文件 用途
创建 aad.template.jsontemplate\appPackage Microsoft Entra应用程序清单表示Microsoft Entra应用。 template\appPackage帮助在本地调试或预配阶段注册Microsoft Entra应用。
修改 manifest.template.jsontemplate\appPackage 对象 webApplicationInfo 将添加到应用清单中, (以前称为 Teams 应用清单) 模板。 Teams 需要此字段才能启用 SSO。 触发本地调试或预配时,更改将生效。
创建 auth\tab 选项卡项目的此路径中会生成引用代码、身份验证重定向页和 README.md 文件。
创建 auth\bot 此路径中为机器人项目生成引用代码、身份验证重定向页和 README.md 文件。

注意

通过添加 SSO,在触发本地调试之前,Teams 工具包不会更改云中的任何内容。 更新代码以确保 SSO 在项目中正常工作。

更新应用程序以使用 SSO

若要在应用程序中启用 SSO,请执行以下步骤:

注意

这些更改基于你搭建基架的模板。




Tab 项目
  1. auth-start.htmlauth-end.htm 复制到 auth\public 文件夹中 tabs\public\。 Teams 工具包在 Microsoft Entra ID 中为Microsoft Entra ID的重定向流注册这两个终结点。

  2. 将 下的文件夹复制到 ssotabs\src\sso\auth\tab

    • InitTeamsFx:该文件实现一个函数,该函数初始化 TeamsFx SDK 并在 SDK 初始化后打开 GetUserProfile 组件。

    • GetUserProfile:该文件实现调用 Microsoft 图形 API 以获取用户信息的函数。

  3. 在 下tabs\执行npm install @microsoft/teamsfx-react

  4. 将以下行添加到 以 tabs\src\components\sample\Welcome.tsx 导入 InitTeamsFx

    
    import { InitTeamsFx } from "../../sso/InitTeamsFx";
    
    
  5. 将以下行:

    <AddSSO /><InitTeamsFx /> 组件替换为 AddSsoInitTeamsFx 组件。

命令机器人项目

设置Microsoft Entra ID重定向

  1. auth\bot\public 文件夹移动到 bot\src。 此文件夹包含机器人应用托管的 HTML 页面。 使用 Microsoft Entra ID 启动 SSO 流时,会将你重定向到 HTML 页面。

  2. 修改 以 bot\src\index 将相应的 restify 路由添加到 HTML 页面。

    const path = require("path");
    
    server.get(
      "/auth-*.html",
      restify.plugins.serveStatic({
        directory: path.join(__dirname, "public"),
      })
    );
    

更新应用

SSO 命令处理程序ProfileSsoCommandHandler使用 Microsoft Entra 令牌来调用 Microsoft Graph。 此令牌是使用已登录的 Teams 用户令牌获取的。 如有必要,该流将汇集在显示同意对话框的对话框中。

  1. 将文件夹下auth\bot\sso的文件移动到 bot\srcprofileSsoCommandHandlerProfileSsoCommandHandler 类是一个 SSO 命令处理程序,用于获取具有 SSO 令牌的用户信息,遵循此方法并创建自己的 SSO 命令处理程序。

  2. 打开 package.json 文件并确保 TeamsFx SDK 版本 >= 1.2.0。

  3. npm install isomorphic-fetch --save 文件夹中执行 命令 bot

  4. 对于 ts 脚本,请在 文件夹中执行 npm install copyfiles --save-dev 命令, bot 并在 中 package.json替换以下行:

    "build": "tsc --build && shx cp -r ./src/adaptiveCards ./lib/src",
    

    "build": "tsc --build && shx cp -r ./src/adaptiveCards ./lib/src && copyfiles src/public/*.html lib/",
    

    这会复制生成机器人项目时用于身份验证重定向的 HTML 页面。

  5. 若要使 SSO 同意流正常工作,请替换 文件中的以下代码 bot\src\index

    server.post("/api/messages", async (req, res) => {
      await commandBot.requestHandler(req, res);
    });
    

    server.post("/api/messages", async (req, res) => {
      await commandBot.requestHandler(req, res).catch((err) => {
        // Error message including "412" means it is waiting for user's consent, which is a normal process of SSO, shouldn't throw this error.
        if (!err.message.includes("412")) {
          throw err;
        }
      });
    });
    
  6. 替换 中的bot\src\internal\initialize实例的以下选项ConversationBot以添加 SSO 配置和 SSO 命令处理程序:

    export const commandBot = new ConversationBot({
        ...
        command: {
            enabled: true,
            commands: [new HelloWorldCommandHandler()],
        },
    });
    

    import { ProfileSsoCommandHandler } from "../profileSsoCommandHandler";
    
    export const commandBot = new ConversationBot({
        ...
        // To learn more about ssoConfig, please refer teamsfx sdk document: https://docs.microsoft.com/microsoftteams/platform/toolkit/teamsfx-sdk
        ssoConfig: {
            aad :{
                scopes:["User.Read"],
            },
        },
        command: {
            enabled: true,
            commands: [new HelloWorldCommandHandler() ],
            ssoCommands: [new ProfileSsoCommandHandler()],
        },
    });
    
  7. 在 Teams 应用清单中注册命令。 打开 templates\appPackage\manifest.template.json,并在机器人的 中commandLists添加以下行commands

    {
      "title": "profile",
      "description": "Show user profile using Single Sign On feature"
    }
    

将新的 SSO 命令添加到机器人 (可选)

在项目中成功添加 SSO 后,可以添加新的 SSO 命令:

  1. 在 中创建一个新文件(如 photoSsoCommandHandler.tsphotoSsoCommandHandler.js ),bot\src\并添加自己的 SSO 命令处理程序以调用 图形 API:

    // for TypeScript:
    import { Activity, TurnContext, ActivityTypes } from "botbuilder";
    import "isomorphic-fetch";
    import {
        CommandMessage,
        TriggerPatterns,
        TeamsFx,
        createMicrosoftGraphClient,
        TeamsFxBotSsoCommandHandler,
        TeamsBotSsoPromptTokenResponse,
    } from "@microsoft/teamsfx";
    
    export class PhotoSsoCommandHandler implements TeamsFxBotSsoCommandHandler {
        triggerPatterns: TriggerPatterns = "photo";
    
        async handleCommandReceived(
            context: TurnContext,
            message: CommandMessage,
            tokenResponse: TeamsBotSsoPromptTokenResponse,
        ): Promise<string | Partial<Activity> | void> {
            await context.sendActivity("Retrieving user information from Microsoft Graph ...");
    
            const teamsfx = new TeamsFx().setSsoToken(tokenResponse.ssoToken);
    
            const graphClient = createMicrosoftGraphClient(teamsfx, ["User.Read"]);
    
            let photoUrl = "";
            try {
                const photo = await graphClient.api("/me/photo/$value").get();
                const arrayBuffer = await photo.arrayBuffer();
                const buffer=Buffer.from(arrayBuffer, 'binary');
                photoUrl = "data:image/png;base64," + buffer.toString("base64");
            } catch {
                // Could not fetch photo from user's profile, return empty string as placeholder.
            }
            if (photoUrl) {
                const photoMessage: Partial<Activity> = {
                    type: ActivityTypes.Message,
                    text: 'This is your photo:',
                    attachments: [
                        {
                            name: 'photo.png',
                            contentType: 'image/png',
                            contentUrl: photoUrl
                        }
                    ]
                };
                return photoMessage;
            } else {
                return "Could not retrieve your photo from Microsoft Graph. Please make sure you have uploaded your photo.";
            }
        }
    }
    
    // for JavaScript:
    const { ActivityTypes } = require("botbuilder");
    require("isomorphic-fetch");
    const {
      createMicrosoftGraphClient,
      TeamsFx,
    } = require("@microsoft/teamsfx");
    
    class PhotoSsoCommandHandler {
      triggerPatterns = "photo";
    
      async handleCommandReceived(context, message, tokenResponse) {
        await context.sendActivity(
          "Retrieving user information from Microsoft Graph ..."
        );
    
        const teamsfx = new TeamsFx().setSsoToken(tokenResponse.ssoToken);
    
        const graphClient = createMicrosoftGraphClient(teamsfx, ["User.Read"]);
    
        let photoUrl = "";
        try {
          const photo = await graphClient.api("/me/photo/$value").get();
          const arrayBuffer = await photo.arrayBuffer();
          const buffer = Buffer.from(arrayBuffer, "binary");
          photoUrl = "data:image/png;base64," + buffer.toString("base64");
        } catch {
          // Could not fetch photo from user's profile, return empty string as placeholder.
        }
        if (photoUrl) {
          const photoMessage = {
            type: ActivityTypes.Message,
            text: "This is your photo:",
            attachments: [
              {
                name: "photo.png",
                contentType: "image/png",
                contentUrl: photoUrl,
              },
            ],
          };
          return photoMessage;
        } else {
          return "Could not retrieve your photo from Microsoft Graph. Please make sure you have uploaded your photo.";
        }
      }
    }
    
    module.exports = {
      PhotoSsoCommandHandler,
    };
    
  2. 将实例添加到 PhotoSsoCommandHandlerssoCommands 中的数组:bot\src\internal\initialize.ts

    // for TypeScript:
    import { PhotoSsoCommandHandler } from "../photoSsoCommandHandler";
    
    export const commandBot = new ConversationBot({
        ...
        command: {
            ...
            ssoCommands: [new ProfileSsoCommandHandler(), new PhotoSsoCommandHandler()],
        },
    });
    
    // for JavaScript:
    ...
    const { PhotoSsoCommandHandler } = require("../photoSsoCommandHandler");
    
    const commandBot = new ConversationBot({
        ...
        command: {
            ...
            ssoCommands: [new ProfileSsoCommandHandler(), new PhotoSsoCommandHandler()]
        },
    });
    ...
    
    
  3. 在应用清单中注册命令。 打开 templates\appPackage\manifest.template.json,并在机器人的 下面commandLists添加以下行commands

    
    {
        "title": "photo",
        "description": "Show user photo using Single Sign On feature"
    }
    
    
消息扩展项目

示例业务逻辑提供了一个扩展和重写 handleTeamsMessagingExtensionQueryTeamsActivityHandler处理程序TeamsBot

可以使用令牌更新 中的 handleMessageExtensionQueryWithToken 查询逻辑,该令牌是使用登录的 Teams 用户令牌获取的。

若要在应用中实现此工作,请执行以下操作:

  1. auth\bot\public 文件夹移动到 bot。 此文件夹包含机器人应用托管的 HTML 页面。 使用 Microsoft Entra ID 启动 SSO 流时,Microsoft Entra ID会将你重定向到这些页面。

  2. 修改 以 bot\index 向这些页面添加相应的 restify 路由。

    const path = require("path");
    
    server.get(
        "/auth-*.html",
        restify.plugins.serveStatic({
            directory: path.join(__dirname, "public"),
        })
    );
    
  3. 重写 handleTeamsMessagingExtensionQuery 下的 bot\teamsBot接口。 可以按照 中的 handleMessageExtensionQueryWithToken 示例代码执行自己的查询逻辑。

  4. 打开 bot\package.json,确保 @microsoft/teamsfx 版本 >= 1.2.0

  5. 在机器人项目中安装 isomorphic-fetch npm 包。

  6. 对于 ts,请在机器人项目中安装 copyfiles npm 包,在 中添加bot\package.json或更新脚本,build如下所示:

    "build": "tsc --build && copyfiles ./public/*.html lib/",
    

    执行此操作时,在生成此机器人项目时,将复制用于身份验证重定向的 HTML 页面。

  7. 更新 templates\appPackage\aad.template.json 在 中使用的 handleMessageExtensionQueryWithToken范围。

    "requiredResourceAccess": [
        {
            "resourceAppId": "Microsoft Graph",
            "resourceAccess": [
                {
                    "id": "User.Read",
                    "type": "Scope"
                }
            ]
        }
    ]
    

调试应用

F5 调试应用程序。 Teams 工具包使用 Microsoft Entra 应用清单文件为 SSO 注册Microsoft Entra应用。 有关 Teams 工具包本地调试功能,请参阅 如何在本地调试应用

自定义Microsoft Entra应用注册

Microsoft Entra应用清单允许自定义应用注册的各个方面。 可以根据需要更新应用清单。 如果需要包含更多 API 权限来访问所需 API,请参阅 API 权限以访问所需 API。 若要在 Azure 门户 中查看Microsoft Entra应用,请参阅在 Azure 门户 中查看Microsoft Entra应用

SSO 身份验证概念

以下概念有助于执行 SSO 身份验证:

在 Teams 中运行 SSO

Microsoft Entra ID中的 SSO 身份验证以静默方式刷新身份验证令牌,以最大程度地减少用户需要输入其登录凭据的次数。 如果用户同意使用你的应用,则无需在另一台设备上再次提供同意,因为他们会自动登录。

Teams 选项卡和机器人具有类似的 SSO 支持流。 有关更多信息,请参阅:

  1. 选项卡中的 SSO 身份验证
  2. 机器人中的 SSO 身份验证

TeamsFx 的简化 SSO

TeamsFx 通过使用 SSO 和访问云资源(以零配置)减少到单行语句,从而帮助减少开发人员任务。

使用 TeamsFx SDK,可以使用以下凭据以简化的方式编写用户身份验证代码:

  1. 浏览器环境中的用户标识: TeamsUserCredential 表示 Teams 当前用户的标识。
  2. Node.js 环境中的用户标识: OnBehalfOfUserCredential 使用代理流和 SSO 令牌。
  3. Node.js 环境中的应用程序标识: AppCredential 表示应用程序标识。

有关 TeamsFx SDK 的详细信息,请参阅:

另请参阅