练习 - 将 API 插件与使用 OAuth 保护的 API 集成

已完成

智能 Microsoft 365 Copilot 副驾驶®的 API 插件允许你与使用 OAuth 保护的 API 集成。 通过在 Teams 保管库中注册来保护 API 安全的应用的客户端 ID 和机密,可以保留这些 ID 和机密。 在运行时,智能 Microsoft 365 Copilot 副驾驶®执行插件,从保管库中检索信息,并使用它获取访问令牌并调用 API。 通过遵循此过程,客户端 ID 和机密将保持安全,永远不会向客户端公开。

打开示例项目

首先下载示例项目:

  1. 在 Web 浏览器中,转到 https://aka.ms/learn-da-api-ts-repairs。 系统会提示下载包含示例项目的 ZIP 文件。
  2. 在计算机上保存 ZIP 文件。
  3. 提取 ZIP 文件内容。
  4. 在 Visual Studio Code 中打开文件夹。

示例项目是一个 Microsoft 365 Agents Toolkit 项目,其中包括声明性代理、API 插件和使用Microsoft Entra ID保护的 API。 API 在Azure Functions上运行,并使用Azure Functions的内置身份验证和授权功能(有时称为“轻松身份验证”)实现安全性。

检查 OAuth2 授权配置

在继续之前,请检查示例项目中的 OAuth2 授权配置。

检查 API 定义

首先,查看项目中包含的 API 定义的安全配置。

在 Visual Studio Code:

  1. 打开 appPackage/apiSpecificationFile/repair.yml 文件。

  2. components.securitySchemes 部分中,请注意 oAuth2AuthCode 属性:

    components:
      securitySchemes:
        oAuth2AuthCode:
          type: oauth2
          description: OAuth configuration for the repair service
          flows:
            authorizationCode:
              authorizationUrl: https://login.microsoftonline.com/${{AAD_APP_TENANT_ID}}/oauth2/v2.0/authorize
              tokenUrl: https://login.microsoftonline.com/${{AAD_APP_TENANT_ID}}/oauth2/v2.0/token
              scopes:
                api://${{AAD_APP_CLIENT_ID}}/repairs_read: Read repair records 
    

    属性定义 OAuth2 安全方案,并包含有关要调用以获取访问令牌的 URL 以及 API 使用的范围的信息。

    重要

    请注意,范围使用应用程序 ID URI (api://...) 进行完全限定。使用Microsoft Entra需要完全限定自定义范围。 当Microsoft Entra看到非限定范围时,它会假定它属于 Microsoft Graph,这会导致授权流错误。

  3. 找到 paths./repairs.get.security 属性。 请注意,它引用了客户端执行作所需的 oAuth2AuthCode 安全方案和范围。

    [...]
    paths:
      /repairs:
        get:
          operationId: listRepairs
          [...]
          security:
            - oAuth2AuthCode:
              - api://${{AAD_APP_CLIENT_ID}}/repairs_read
    [...]
    

    重要

    在 API 规范中列出必要的范围纯粹是信息性的。 实现 API 时,你负责验证令牌并检查它是否包含必要的范围。

检查 API 实现

接下来,查看 API 实现。

在 Visual Studio Code:

  1. 打开 src/functions/repairs.ts 文件。

  2. 修复 处理程序函数中,找到以下行,用于检查请求是否包含具有必要范围的访问令牌:

    if (!hasRequiredScopes(req, 'repairs_read')) {
      return {
        status: 403,
        body: "Insufficient permissions",
      };
    }
    
  3. hasRequiredScopes 函数在 repairs.ts 文件中进一步实现:

    function hasRequiredScopes(req: HttpRequest, requiredScopes: string[] | string): boolean {
      if (typeof requiredScopes === 'string') {
        requiredScopes = [requiredScopes];
      }
    
      const token = req.headers.get("Authorization")?.split(" ");
      if (!token || token[0] !== "Bearer") {
        return false;
      }
    
      try {
        const decodedToken = jwtDecode<JwtPayload & { scp?: string }>(token[1]);
        const scopes = decodedToken.scp?.split(" ") ?? [];
        return requiredScopes.every(scope => scopes.includes(scope));
      }
      catch (error) {
        return false;
      }
    }
    

    函数首先从授权请求标头中提取持有者令牌。 接下来,它使用 jwt-decode 包解码令牌并从 scp 声明获取范围列表。 最后,它会检查 scp 声明是否包含所有必需的范围。

    请注意,函数未验证访问令牌。 相反,它仅检查访问令牌是否包含所需的范围。 在此模板中,API 在 Azure Functions上运行,并使用负责验证访问令牌的 Easy Auth 实现安全性。 如果请求不包含有效的访问令牌,Azure Functions运行时会在它到达代码之前拒绝它。 虽然 Easy Auth 会验证令牌,但它不会检查所需的范围,而你需要自己执行。

检查保管库任务配置

在此项目中,使用 Microsoft 365 代理工具包将 OAuth 信息添加到保管库。 Microsoft 365 代理工具包使用项目配置中的特殊任务在保管库中注册 OAuth 信息。

在 Visual Studio Code:

  1. 打开 ./teampsapp.local.yml 文件。

  2. 预配 部分中,找到 oauth/register 任务。

    - uses: oauth/register
      with:
        name: oAuth2AuthCode
        flow: authorizationCode
        appId: ${{TEAMS_APP_ID}}
        clientId: ${{AAD_APP_CLIENT_ID}}
        clientSecret: ${{SECRET_AAD_APP_CLIENT_SECRET}}
        isPKCEEnabled: true
        # Path to OpenAPI description document
        apiSpecPath: ./appPackage/apiSpecificationFile/repair.yml
      writeToEnvironmentFile:
        configurationId: OAUTH2AUTHCODE_CONFIGURATION_ID
    

    该任务采用存储在 env/.env.local 和 env/.env.local.user 文件中的 TEAMS_APP_IDAAD_APP_CLIENT_IDSECRET_AAD_APP_CLIENT_SECRET项目变量的值,并将其注册到保管库中。 它还为代码交换 (PKCE) 启用证明密钥作为额外的安全措施。 然后,它获取保管库条目 ID 并将其写入环境文件 env/.env.local。 此任务的结果是名为 OAUTH2AUTHCODE_CONFIGURATION_ID 的环境变量。 Microsoft 365 Agents Toolkit 将此变量的值写入包含插件定义的 appPackages/ai-plugin.json 文件。 在运行时,加载 API 插件的声明性代理使用此 ID 从保管库中检索 OAuth 信息,并启动和身份验证流以获取访问令牌。

    重要

    oauth/register 任务仅负责在保管库中注册 OAuth 信息(如果尚不存在)。 如果信息已存在,Microsoft 365 代理工具包将跳过运行此任务。

  3. 接下来,找到 oauth/update 任务。

    - uses: oauth/update
      with:
        name: oAuth2AuthCode
        appId: ${{TEAMS_APP_ID}}
        apiSpecPath: ./appPackage/apiSpecificationFile/repair.yml
        configurationId: ${{OAUTH2AUTHCODE_CONFIGURATION_ID}}
        isPKCEEnabled: true
    

    该任务使保管库中的 OAuth 信息与项目保持同步。 项目必须正常工作。 其中一个关键属性是 API 插件可用的 URL。 每次启动项目时,Microsoft 365 Agents Toolkit 都会在新 URL 上打开一个开发隧道。 保管库中的 OAuth 信息需要引用此 URL,Copilot 才能访问 API。

检查身份验证和授权配置

下一部分是Azure Functions的身份验证和授权设置。 本练习中的 API 使用 Azure Functions 的内置身份验证和授权功能。 Microsoft 365 代理工具包在将Azure Functions预配到 Azure 时配置这些功能。

在 Visual Studio Code:

  1. 打开 infra/azure.bicep 文件。

  2. 找到 authSettings 资源:

    resource authSettings 'Microsoft.Web/sites/config@2021-02-01' = {
      parent: functionApp
      name: 'authsettingsV2'
      properties: {
        globalValidation: {
          requireAuthentication: true
          unauthenticatedClientAction: 'Return401'
        }
        identityProviders: {
          azureActiveDirectory: {
            enabled: true
            registration: {
              openIdIssuer: oauthAuthority
              clientId: aadAppClientId
            }
            validation: {
              allowedAudiences: [
                aadAppClientId
                aadApplicationIdUri
              ]
            }
          }
        }
      }
    }
    

    此资源在 Azure Functions 应用上启用内置身份验证和授权功能。 首先,在 globalValidation 部分中,它定义应用仅允许经过身份验证的请求。 如果应用收到未经身份验证的请求,则会拒绝并显示 401 HTTP 错误。 然后,在 identityProviders 部分中,配置定义它使用以前称为 Azure Active Directory) Microsoft Entra ID (来授权请求。 它指定它使用哪些Microsoft Entra应用注册来保护 API,以及允许哪些访问群体调用 API。

检查Microsoft Entra应用程序注册

要检查的最后一部分是项目用于保护 API 的Microsoft Entra应用程序注册。 使用 OAuth 时,可以使用应用程序保护对资源的访问。 应用程序通常定义获取访问令牌所需的凭据,例如客户端密码或证书。 它还指定客户端在调用 API 时可以请求的不同权限 (也称为作用域) 。 Microsoft Entra应用程序注册表示Microsoft云中的应用程序,并定义用于 OAuth 授权流的应用程序。

在 Visual Studio Code:

  1. 打开 ./aad.manifest.json 文件。

  2. 找到 oauth2Permissions 属性。

    "oauth2Permissions": [
      {
        "adminConsentDescription": "Allows Copilot to read repair records on your behalf.",
        "adminConsentDisplayName": "Read repairs",
        "id": "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}",
        "isEnabled": true,
        "type": "User",
        "userConsentDescription": "Allows Copilot to read repair records.",
        "userConsentDisplayName": "Read repairs",
        "value": "repairs_read"
      }
    ],
    

    属性定义名为 repairs_read 的自定义范围,该作用域授予客户端从修复 API 读取修复的权限。

  3. 找到 identifierUris 属性。

    "identifierUris": [
      "api://${{AAD_APP_CLIENT_ID}}"
    ]
    

    identifierUris 属性定义用于完全限定范围的标识符。

在 智能 Microsoft 365 Copilot 副驾驶® 中使用 API 插件测试声明性代理

最后一步是使用 智能 Microsoft 365 Copilot 副驾驶® 中的 API 插件测试声明性代理。

在 Visual Studio Code:

  1. 在活动栏中,激活 Microsoft 365 代理工具包 扩展。

  2. Microsoft 365 代理工具包 扩展面板中的 “帐户 ”部分中,确保已登录到启用了 Copilot 的 Microsoft 365 租户。

    Microsoft 365 代理工具包的屏幕截图,其中显示了与 Microsoft 365 的连接状态。

  3. 在活动栏中,切换到 “运行和调试” 视图。

  4. 从配置列表中,选择 “在 Copilot (Edge) 中调试 ”,然后按播放按钮开始调试。

    Visual Studio Code中调试选项的屏幕截图。

    Visual Studio Code使用智能 Microsoft 365 Copilot 副驾驶®打开新的 Web 浏览器。 如果出现提示,请使用 Microsoft 365 帐户登录。

在 Web 浏览器中:

  1. 在侧面板中,选择 da-repairs-oauthlocal 代理。

    智能 Microsoft 365 Copilot 副驾驶®中显示的自定义代理的屏幕截图。

  2. 在提示文本框中,键入 Show repair records assigned to Karin Blair 并提交提示。

    提示

    你可以从对话初学者中选择它,而不是键入提示。

    自定义声明性代理中启动的对话的屏幕截图。

  3. 确认要使用 “始终允许 ”按钮将数据发送到 API 插件。

    允许将数据发送到 API 的提示的屏幕截图。

  4. 出现提示时,通过选择“登录到 da-repairs-oauthlocal”,登录到 API 以继续使用用于登录到 Microsoft 365 租户的同一帐户。

    提示登录到保护 API 的应用的屏幕截图。

  5. 等待代理响应。

    声明性代理对用户提示的响应的屏幕截图。

尽管 API 在本地计算机上运行,因此可以匿名访问,但智能 Microsoft 365 Copilot 副驾驶®调用 API 规范中指定的身份验证 API。可以通过在 repairs 函数中设置断点并在声明性代理中提交另一个提示来验证请求是否包含访问令牌。 当代码到达断点时,展开 req.headers 集合并查找包含 JSON Web 令牌 (JWT) 的授权标头。

包含断点和调试面板的Visual Studio Code的屏幕截图,其中显示了传入请求的授权标头。

完成测试后,停止Visual Studio Code中的调试会话。