使用 Azure API 管理和 Azure AD B2C 保护无服务器 API,以便从 SPA 使用

适用于:所有 API 管理层级

重要

自 2025 年 5 月 1 日起,Azure AD B2C 将不再可供新客户购买。 在我们的常见问题解答中了解详细信息

此方案说明如何配置 Azure API 管理实例以保护 API。 我们使用 Azure AD B2C SPA(身份验证代码 + PKCE)流获取令牌,以及 API 管理,以使用 EasyAuth 保护 Azure Functions 后端。

有关 API 授权的概念概述,请参阅 API 管理中对 API 的身份验证和授权

目的

我们将了解如何通过 Azure Functions 和 Azure AD B2C 在简化方案中使用 API 管理。 你需要创建一个调用 API 的 JavaScript (JS) 应用,它使用 Azure AD B2C 登录用户。 然后,你将使用 API 管理服务的 validate-jwt、CORS 和“按密钥限制速率”策略功能来保护后端 API。

为了深度防御,我们随后会使用 EasyAuth 在后端 API 中再次验证令牌,并确保只有 API 管理服务可调用 Azure Functions 后端。

你将学到什么

  • 在 Azure Active Directory B2C 中设置单页应用和后端 API
  • 创建 Azure Functions 后端 API
  • 将 Azure Functions API 导入 Azure API 管理
  • 在 Azure API 管理中实现 API 安全性
  • 通过 Microsoft 标识平台库 (MSAL.js) 调用 Azure Active Directory B2C 授权终结点
  • 存储 HTML/Vanilla JS 单页应用程序并从 Azure Blob 存储端点提供服务

先决条件

若要执行本文中的步骤,必须提供:

  • 一个 Azure (StorageV2) 常规用途 V2 存储帐户,用于托管前端 JS 单页应用。
  • Azure API 管理实例(任何层都有效,包括“消耗”),但是适用于完整方案的某些功能在此层(按密钥速率限制和专用虚拟 IP)中不可用,因此,在适当情况下,本文中将在下面调用这些限制。
  • 一个空的 Azure 函数应用(在消耗计划中运行 V3.1 .NET Core 运行时),用于托管调用 API
  • 一个 Azure AD B2C 租户,它与某订阅相关联。

尽管实际上,在生产工作负荷中,你将在同一区域中使用资源,但对于本作指南文章,部署区域并不重要。

概述

下图显示了正在使用的组件以及该过程完成后这些组件之间的流。 组件的使用和流动过程

下面是简要的步骤概述:

  1. 创建具有范围的 Azure AD B2C 调用(前端、API 管理)和 API 应用程序,并授予 API 访问权限

  2. 创建注册和登录策略,以允许用户使用 Azure AD B2C 进行登录

  3. 使用新的 Azure AD B2C 客户端 ID 和密钥配置 API 管理,以在开发人员控制台中启用 OAuth2 用户授权

  4. 生成函数 API

  5. 配置函数 API 以使用新的 Azure AD B2C 客户端 ID 和密钥启用 EasyAuth 并锁定到 APIM VIP

  6. 在 API 管理中生成 API 定义

  7. 为 API 管理 API 配置设置 OAuth2

  8. 设置 CORS 策略,并添加 validate-jwt 策略以验证每个传入请求的 OAuth 令牌

  9. 生成调用应用程序以使用 API

  10. 上传 JS SPA 示例

  11. 使用新的 Azure AD B2C 客户端 ID 和密钥配置示例 JS 客户端应用

  12. 测试客户端应用程序

    提示

    我们将收集多种信息、密钥等内容。浏览本文档时,你可能会觉得打开一个文本编辑器来临时存储以下的配置项会很方便。

    B2C 后端客户端 ID:B2C 后端客户端密钥:B2C 后端 API 范围 URI:B2C 前端客户端 ID:B2C 用户流终结点 URI:B2C 已知 OPENID 终结点:B2C 策略名称:Frontendapp_signupandsignin 函数 URL:APIM API 基 URL:存储主终结点 URL:

配置后端应用程序

在门户中打开“Azure AD B2C”边栏选项卡,然后执行以下步骤。

  1. 选择“应用注册”选项卡

  2. 选择“新建注册”按钮。

  3. 从“重定向 URI”选择框中,选择“Web”。

  4. 现在设置显示名称,请选择一个与正在创建的服务相关的唯一名称。 在本示例中,使用的名称是“后端应用程序”。

  5. 使用回复 URL 的占位符,例如“https://jwt.ms”(Microsoft 拥有的令牌解码网站),我们稍后将更新这些 URL。

  6. 确保已选择“任何标识提供者或组织目录中的帐户(用于通过用户流对用户进行身份验证)”选项

  7. 对于本示例,请取消选中“授予管理员同意”框,因为目前不需要 offline_access 权限。

  8. 选择“注册”。

  9. 记录后端应用程序客户端 ID 供以后使用,它显示在“应用程序(客户端)ID”下。

  10. 选择“ 证书和机密 ”选项卡(在“管理”下),然后选择“新建客户端密码”以生成身份验证密钥(接受默认设置并选择“添加”。

  11. 单击“添加”后,将密钥(在“value”下)复制到安全的地方,以便稍后用作“后端客户端机密”-请注意,此对话框是唯一复制此密钥的机会。

  12. 现在,在“管理”下选择“公开 API”选项卡。

  13. 系统会提示设置 AppID URI,选择并记录默认值。

  14. 为函数 API 创建并命名范围“Hello”,可以对所有可输入的选项使用短语“Hello”,录制填充的完整范围值 URI,然后选择“添加范围”。

  15. 选择门户左上角的“Azure AD B2C”痕迹导航栏,返回到 Azure AD B2C 边栏选项卡的根目录。

    备注

    Azure AD B2C 范围在您的 API 中充当权限,其他应用程序可以通过其应用的“API 访问”选项卡请求此权限。因此,实际上您为调用的 API 创建了应用程序权限。

配置前端应用程序

  1. 选择“应用注册”选项卡
  2. 选择“新建注册”按钮。
  3. 从“重定向 URI”选择框中,选择“单页应用程序(SPA)”。
  4. 现在设置显示名称和 AppID URI,请选择一个与将使用此 Azure Active Directory B2C 应用注册的前端应用程序相关的唯一名称。 在此示例中,可使用“前端应用程序”
  5. 如同首次应用注册一样,将支持的帐户类型选择保留为默认值(通过用户流对用户进行身份验证)
  6. 使用回复 URL 的占位符,例如“https://jwt.ms”(Microsoft 拥有的令牌解码网站),我们稍后将更新这些 URL。
  7. 将“授予管理员同意”框保留在勾选状态
  8. 选择“注册”。
  9. 记录前端应用程序客户端 ID 供以后使用,它显示在“应用程序(客户端)ID”下。
  10. 切换到“API 权限”选项卡。
  11. 通过单击“添加权限”进入后端应用程序,然后选择“我的 API”,接着选择“后端应用程序”,进入“权限”页面,选择上一部分中创建的作用域,最后选择“添加权限”以完成操作。
  12. 选择“为 {tenant} 授予管理员同意”,然后从弹出对话框中选择“是”。 此弹出窗口同意“前端应用程序”使用在之前创建的“后端应用程序”中定义的“hello”权限。
  13. 现在,应用的所有权限应会在状态列下显示为绿色勾号

创建“注册和登录”用户流

  1. 选择 Azure AD B2C 痕迹导航栏,返回到 B2C 边栏选项卡的根目录。

  2. 切换到“策略”下的“用户流”选项卡。

  3. 选择“新建用户流”

  4. 选择“注册和登录”用户流类型,选择“推荐”,然后选择“创建”

  5. 为策略指定一个名称并记录,以供以后使用。 对于这个例子,你可以使用“Frontendapp_signupandsignin”,要注意这个名称将有前缀“B2C_1_”,变成“B2C_1_Frontendapp_signupandsignin”。

  6. 在“标识提供者”和“本地帐户”下,检查“电子邮件注册”(或“用户 ID 注册”,具体取决于 B2C 租户的配置),然后选择“确定”。 使用此配置是因为我们将注册本地 B2C 帐户,而不是推延给其他标识提供者(如社交标识提供者)来使用用户的现有社交媒体帐户。

  7. 将 MFA 和条件访问设置保留在默认状态下。

  8. 在“用户属性和声明”下,选择“显示更多...”。然后选择你希望用户输入并在令牌中返回的声明选项。 检查至少要收集的“显示名称”和“电子邮件地址”,并返回“显示名称”和“电子邮件地址”(请注意收集电子邮件地址、单数和要求返回电子邮件地址的事实),然后选择“确定”,然后选择“创建”。

  9. 选择在列表中创建的用户流,然后选择“运行用户流”按钮。

  10. 此操作将打开“运行用户流” blade,选择前端应用程序,复制用户流终结点并保存以供日后使用。

  11. 复制并存储顶部的链接,将其记录为“已知的 OpenID 配置终结点”供以后使用。

    备注

    使用 B2C 策略可以公开 Azure AD B2C 登录终结点,使其能够捕获不同的数据组件,并以不同的方式使用户登录。

    在本例中,我们配置了注册或登录流(策略)。 这也公开了已知的配置终结点,在这两种情况下,我们创建的策略都通过“p=”查询字符串参数在 URL 中进行了标识。

    完成此操作后,你现在就有了一个功能性的企业对消费者标识平台,可将用户登录到多个应用程序。

生成函数 API

  1. 切换回到 Azure 门户中的标准 Microsoft Entra 租户,以便我们可再次配置你订阅中的项。

  2. 转到 Azure 门户的“函数应用”边栏选项卡,打开空的函数应用,然后选择“Functions”,选择“添加”。

  3. 在显示的浮出控件中,选择“在门户中开发”,在“选择模板”下,选择“HTTP 触发器”,在模板详细信息下将其命名为“hello”,其授权级别为“Function”,然后选择“添加”。

  4. 切换到“代码 + 测试”边栏选项卡,并将下面的示例代码复制粘贴到显示的现有代码的上方

  5. 选择“保存”。

    
    using System.Net;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Primitives;
    
    public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
    {
       log.LogInformation("C# HTTP trigger function processed a request.");
    
       return (ActionResult)new OkObjectResult($"Hello World, time and date are {DateTime.Now.ToString()}");
    }
    
    

    提示

    刚粘贴的 C# 脚本函数代码仅向函数日志中记录一行内容,并返回文本“Hello World”,其中包含一些动态数据(日期和时间)。

  6. 从左侧面板中选择“集成”,然后在“触发器”框中选择 http (req) 链接。

  7. 在“选定的 HTTP 方法”下拉列表中,取消选中 http POST 方法,仅选择 GET,然后选择“保存”。

  8. 切换回“代码 + 测试”选项卡,选择“获取函数 URL”,然后复制显示的 URL 并将其保存供以后使用。

    备注

    你刚才创建的绑定只会指示 Functions 对你刚才复制的 URL (https://yourfunctionappname.chinacloudsites.cn/api/hello?code=secretkey) 的匿名 HTTP GET 请求做出响应。 现在,我们有一个可缩放的无服务器 https API,能够返回简单的有效负载。

    你现可使用上面刚复制和保存的 URL 版本,测试从 Web 浏览器调用此 API 的情况。 还可以删除 URL 的查询字符串参数“?code=secretkey”部分,然后再次进行测试,以证明 Azure Functions 将返回 401 错误。

配置和保护函数 API

  1. 需要配置函数应用中的两个额外区域(身份验证和网络限制)。

  2. 首先,让我们来配置身份验证/授权,请通过痕迹导航栏导航回到函数应用的根边栏选项卡。

  3. 下一步在“设置”下选择“身份验证”。

  4. 选择“添加标识提供者”

  5. 在“标识提供者”下拉列表中选择“Microsoft”

  6. 对于应用注册,请选择“提供现有应用注册的详细信息”

  7. 将后端应用程序的客户端 ID(从 Azure AD B2C)粘贴到“应用程序(客户端)ID”框中(我们之前记录了此配置)。

  8. 将已知的 OpenID 配置终结点从注册和登录策略中粘贴到“颁发者 URL”框中(前面已记录此配置)。

  9. 将后端应用程序的客户端 ID 粘贴到相应框中(我们之前记录了此配置)。

  10. 对于“未经身份验证的请求”,选择“HTTP 401 未授权:建议用于 API”

  11. 保持令牌商店处于启用状态(默认)。

  12. 选择“保存”(在界面左上角)。

    重要

    现在,你的函数 API 已部署。如果未提供正确的 JWT 作为 Authorization: Bearer 标头,该 API 应引发 401 响应;而在出现有效请求时,它应返回数据。 通过配置“使用 Microsoft Entra ID 登录”选项,已在 EasyAuth 中添加了其他深层防御安全性,以处理未经身份验证的请求。

    我们仍未应用 IP 安全性,如果拥有有效的密钥和 OAuth2 令牌,则任何人都可以从任何位置调用它,理想情况下,我们希望强制所有请求都通过 API 管理传入。

  13. 从应用服务/Functions 门户关闭“身份验证”边栏选项卡。

  14. 打开门户的“API 管理”边栏选项卡,然后打开你的实例 。

  15. 记录“概述”选项卡上显示的专用 VIP。

  16. 返回到门户的“Azure Functions”边栏选项卡,然后再次打开你的实例

  17. 选择“网络”,然后选择“配置访问限制”

  18. 选择“添加规则”,然后在上面的步骤 3 中将复制的 VIP 以 xx.xx.xx.xx/32 的格式输入。

  19. 如果要继续与函数门户交互,并执行以下可选步骤,还应在此处添加自己的公共 IP 地址或 CIDR 范围。

  20. 列表中有允许条目后,Azure 会添加隐式拒绝规则以阻止所有其他地址。

需要将 CIDR 格式化的地址块添加到 IP 限制面板。 如果需要添加单个地址(例如 API 管理 VIP),需要按 xx.xx.xx.xx/32 格式添加它。

备注

现在,除了通过 API 管理或您的地址外,函数 API 不应被其他任何地方调用。

  1. 打开 API 管理工作区,然后打开您的实例。

  2. 在“API”下选择“API”边栏选项卡。

  3. 从“添加新 API”窗格中选择“函数应用”,然后从弹出窗口顶部选择“完整”。

  4. 单击“浏览”,选择要在其中托管 API 的函数应用,然后单击“选择”。 接下来,再次单击“选择”。

  5. 为 API 管理的内部用途提供 API 的名称和描述,并将其添加到“无限制”产品中。

  6. 复制并记录 API 的“基 URL”,然后选择“create”。

  7. 选择“设置”选项卡,然后在订阅下取消选中“需要订阅”复选框,因为我们会在这种情况下使用 OAuth JWT 进行限速。 请注意,如果使用的是消耗层,则在生产环境中仍然需要此项。

    提示

    如果使用 APIM 的消耗层,则不受限的产品将无法开箱即用。 而是导航到“API”下的“产品”,然后点击“添加”。键入“Unlimited”作为产品名称和说明,并从屏幕左下角的“+”API 标注中选择刚添加的 API。 选择“已发布”复选框。 将其余设置保留为默认值。 最后,点击“创建”按钮。 这会创建“不受限”产品并将其分配给 API。 随后可自定义你的新产品。

配置并捕获正确的存储终结点设置

  1. 在 Azure 门户中打开“存储帐户”边栏选项卡

  2. 选择所创建的帐户,然后从“设置”部分选择“静态网站”边栏选项卡(如果看不到“静态网站”选项,请检查是否已创建 V2 帐户)。

  3. 将静态 Web 托管功能设置为“已启用”,并将索引文档名称设置为“index.html”,然后选择“save”。

  4. 记下“主终结点”的内容供以后使用,因为此位置是托管前端站点的位置。

    提示

    可使用 Azure Blob 存储加 CDN 重写或使用 Azure 应用服务来托管 SPA,不过 Blob 存储的静态网站托管功能提供了一个默认容器来从 Azure 存储中提供静态 Web 内容/HTML/JS/CSS,该功能还将推断默认页面,而无需我们执行任何操作。

设置 CORS 和 validate-jwt 策略

无论使用何种 APIM 层,都应遵循以下部分。 存储帐户 URL 来自于在本文开头的先决条件中提供的存储帐户。

  1. 切换到门户的“API 管理”边栏选项卡,然后打开你的实例。

  2. 选择 API,然后选择“所有 API”。

  3. 在“入站处理”下,选择代码视图按钮“</>”以显示策略编辑器。

  4. 编辑入站部分并粘贴以下 xml,使其类似于以下内容。

  5. 在“策略”中,替换以下参数

  6. 将 {PrimaryStorageEndpoint}(你在上一部分复制的“主存储终结点”)、{b2cpolicy-well-known-openid}(你之前复制的“已知的 OpenID 配置终结点”)和 {backend-api-application-client-id}(后端 API 的 B2C 应用程序/客户端 ID)替换为之前保存的正确值

  7. 如果使用的是 API 管理的消耗层,则应删除“按密钥限制速率”策略,因为使用 Azure API 管理的消耗层时,此策略不可用。

    <inbound>
       <cors allow-credentials="true">
             <allowed-origins>
                 <origin>{PrimaryStorageEndpoint}</origin>
             </allowed-origins>
             <allowed-methods preflight-result-max-age="120">
                 <method>GET</method>
             </allowed-methods>
             <allowed-headers>
                 <header>*</header>
             </allowed-headers>
             <expose-headers>
                 <header>*</header>
             </expose-headers>
         </cors>
       <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid." require-expiration-time="true" require-signed-tokens="true" clock-skew="300">
          <openid-config url="{b2cpolicy-well-known-openid}" />
          <required-claims>
             <claim name="aud">
                <value>{backend-api-application-client-id}</value>
             </claim>
          </required-claims>
       </validate-jwt>
       <rate-limit-by-key calls="300" renewal-period="120" counter-key="@(context.Request.IpAddress)" />
       <rate-limit-by-key calls="15" renewal-period="60" counter-key="@(context.Request.Headers.GetValueOrDefault("Authorization","").AsJwt()?.Subject)" />
    </inbound>
    

    备注

    现在,Azure API 管理能够响应来自 JavaScript SPA 应用的跨源请求,并且它将在将请求转发到函数 API 之前执行 JWT 身份验证令牌的限制、速率限制和预验证。

    恭喜,你现在拥有了 Azure AD B2C、API 管理和 Azure Functions,共同协作以发布、保护和使用 API!

    提示

    如果使用的是 API 管理消耗层,则不会通过 JWT 使用者或传入的 IP 地址来限制速率(“消耗”层目前不支持按密钥限制调用速率策略),你可按调用速率配额进行限制,具体请查看此处。 此示例是一个 JavaScript 单页应用程序,因此我们只对速率限制和计费调用使用 API 管理密钥。 实际的授权和身份验证由 Azure AD B2C 处理并封装在 JWT 中,此过程会进行两次验证,先通过 API 管理验证一次,再通过后端 Azure 函数进行验证。

将 JavaScript SPA 示例上传到静态存储

  1. 仍在存储帐户选项卡中,从“Blob 服务”部分选择“容器”选项卡,然后选择右侧窗格中显示的“$web”容器。

  2. 将以下代码作为 index.html 保存到计算机上的本地文件中,然后将文件 index.html 上传到 $web 容器。

     <!doctype html>
     <html lang="en">
     <head>
          <meta charset="utf-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
          <script type="text/javascript" src="https://alcdn.msauth.net/browser/2.11.1/js/msal-browser.min.js"></script>
     </head>
     <body>
          <div class="container-fluid">
              <div class="row">
                  <div class="col-md-12">
                     <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
                         <div class="container-fluid">
                             <a class="navbar-brand" href="#">Azure Active Directory B2C with Azure API Management</a>
                             <div class="navbar-nav">
                                 <button class="btn btn-success" id="signinbtn"  onClick="login()">Sign In</a>
                             </div>
                         </div>
                     </nav>
                  </div>
              </div>
              <div class="row">
                  <div class="col-md-12">
                      <div class="card" >
                         <div id="cardheader" class="card-header">
                             <div class="card-text"id="message">Please sign in to continue</div>
                         </div>
                         <div class="card-body">
                             <button class="btn btn-warning" id="callapibtn" onClick="getAPIData()">Call API</a>
                             <div id="progress" class="spinner-border" role="status">
                                 <span class="visually-hidden">Loading...</span>
                             </div>
                         </div>
                      </div>
                  </div>
              </div>
          </div>
          <script lang="javascript">
                 // Just change the values in this config object ONLY.
                 var config = {
                     msal: {
                         auth: {
                             clientId: "{CLIENTID}", // This is the client ID of your FRONTEND application that you registered with the SPA type in Azure Active Directory B2C
                             authority:  "{YOURAUTHORITYB2C}", // Formatted as https://{b2ctenantname}.b2clogin.cn/tfp/{b2ctenantguid or full tenant name including partner.onmschina.cn}/{signuporinpolicyname}
                             redirectUri: "{StoragePrimaryEndpoint}", // The storage hosting address of the SPA, a web-enabled v2 storage account - recorded earlier as the Primary Endpoint.
                             knownAuthorities: ["{B2CTENANTDOMAIN}"] // {b2ctenantname}.b2clogin.cn
                         },
                         cache: {
                             cacheLocation: "sessionStorage",
                             storeAuthStateInCookie: false
                         }
                     },
                     api: {
                         scopes: ["{BACKENDAPISCOPE}"], // The scope that we request for the API from B2C, this should be the backend API scope, with the full URI.
                         backend: "{APIBASEURL}/hello" // The location that we'll call for the backend api, this should be hosted in API Management, suffixed with the name of the API operation (in the sample this is '/hello').
                     }
                 }
                 document.getElementById("callapibtn").hidden = true;
                 document.getElementById("progress").hidden = true;
                 const myMSALObj = new msal.PublicClientApplication(config.msal);
                 myMSALObj.handleRedirectPromise().then((tokenResponse) => {
                     if(tokenResponse !== null){
                         console.log(tokenResponse.account);
                         document.getElementById("message").innerHTML = "Welcome, " + tokenResponse.account.name;
                         document.getElementById("signinbtn").hidden = true;
                         document.getElementById("callapibtn").hidden = false;
                     }}).catch((error) => {console.log("Error Signing in:" + error);
                 });
                 function login() {
                     try {
                         myMSALObj.loginRedirect({scopes: config.api.scopes});
                     } catch (err) {console.log(err);}
                 }
                 function getAPIData() {
                     document.getElementById("progress").hidden = false;
                     document.getElementById("message").innerHTML = "Calling backend ... "
                     document.getElementById("cardheader").classList.remove('bg-success','bg-warning','bg-danger');
                     myMSALObj.acquireTokenSilent({scopes: config.api.scopes, account: getAccount()}).then(tokenResponse => {
                         const headers = new Headers();
                         headers.append("Authorization", `Bearer ${tokenResponse.accessToken}`);
                         fetch(config.api.backend, {method: "GET", headers: headers})
                             .then(async (response)  => {
                                 if (!response.ok)
                                 {
                                     document.getElementById("message").innerHTML = "Error: " + response.status + " " + JSON.parse(await response.text()).message;
                                     document.getElementById("cardheader").classList.add('bg-warning');
                                 }
                                 else
                                 {
                                     document.getElementById("cardheader").classList.add('bg-success');
                                     document.getElementById("message").innerHTML = await response.text();
                                 }
                                 }).catch(async (error) => {
                                     document.getElementById("cardheader").classList.add('bg-danger');
                                     document.getElementById("message").innerHTML = "Error: " + error;
                                 });
                     }).catch(error => {console.log("Error Acquiring Token Silently: " + error);
                         return myMSALObj.acquireTokenRedirect({scopes: config.api.scopes, forceRefresh: false})
                     });
                     document.getElementById("progress").hidden = true;
              }
             function getAccount() {
                 var accounts = myMSALObj.getAllAccounts();
                 if (!accounts || accounts.length === 0) {
                     return null;
                 } else {
                     return accounts[0];
                 }
             }
         </script>
      </body>
     </html>
    
  3. 浏览到之前在上一部分存储的静态网站主终结点。

    备注

    恭喜,你刚才将一个 JavaScript 单页应用部署到了 Azure 存储静态内容托管。 我们尚未使用你的 Azure AD B2C 详细信息配置 JS 应用,因此如果你打开页面,它将不起作用。

为 Azure AD B2C 配置 JS SPA

  1. 现在我们知道了所有内容的位置:我们可通过相应的 API 管理 API 地址和正确的 Azure AD B2C 应用程序/客户端 ID 来配置 SPA。
  2. 返回到 Azure 门户中的“存储”边栏选项卡
  3. 在“设置”下选择“容器”
  4. 从列表中选择“$web”容器
  5. 从列表中选择名为 index.html 的 blob对象
  6. 选择“编辑”
  7. 更新 MSAL config 部分中的身份验证值,使其与你之前在 B2C 中注册的前端应用程序相匹配。 使用代码注释获取有关配置值应如何显示的提示。 颁发机构值的格式应为:https://{b2ctenantname}.b2clogin.cn/tfp/{b2ctenantname}.partner.onmschina.cn}/{signupandsigninpolicyname}。如果使用的是示例名,且 B2C 租户名称为“contoso”,那么颁发机构应为“”https://contoso.b2clogin.cn/tfp/contoso.partner.onmschina.cn/Frontendapp_signupandsignin。
  8. 设置 API 的值以匹配您的后端地址(前面记录的 API 基准 URL,以及之前记录的“b2cScopes”值用于后端应用程序)。
  9. 选择“保存”

设置 Azure AD B2C 前端应用的重定向 URI

  1. 打开“Azure AD B2C”边栏选项卡,然后导航到 JavaScript 前端应用程序的应用程序注册。

  2. 选择“重定向 URI”并删除前面输入的占位符“https://jwt.ms”。

  3. 为主(存储)终结点添加新的 URI(删除末尾的正斜杠)。

    备注

    此配置将导致前端应用程序的客户端收到具有 Azure AD B2C 中的相应声明的访问令牌。 SPA 将能够将此作为持有者令牌添加到后端 API 调用的 https 标头中。

    API 管理将先预验证令牌,速率限制 Azure ID(用户)和调用方 IP 地址发出的 JWT 的使用者对终结点的调用(取决于 API 管理的服务层,详见上面的说明),然后再通过请求传递给负责接收的 Azure 函数 API,添加函数安全密钥。 SPA 将在浏览器中呈现响应。

    恭喜,你已配置了可完美协作的 Azure AD B2C、Azure API 管理、Azure Functions、Azure 应用服务授权!

现在,我们有了一个具有简单安全的 API 的简单应用,我们来对其进行测试。

测试客户端应用程序

  1. 打开在之前创建存储帐户中记下的示例应用 URL。
  2. 选择右上角的“登录”,此操作会弹出您的 Azure AD B2C 注册或登录配置文件。
  3. 该应用会根据你的 B2C 账户名称欢迎你。
  4. 现在,选择“调用 API”,页面应使用从安全 API 发送回的值进行更新。
  5. 如果 反复 选择“调用 API”按钮,并且你在 API 管理的开发人员层或更高版本中运行,则应注意解决方案将开始对 API 进行速率限制,并且应在应用中报告具有相应消息的此功能。

大功告成

可以调整和编辑上述步骤,以通过 API 管理实现 Azure AD B2C 的多种不同用途。