通过 OAuth 2.0、Azure Active Directory B2C 和 Azure API 管理保护 SPA 后端Protect SPA backend with OAuth 2.0, Azure Active Directory B2C and Azure API Management

此方案说明如何配置 Azure API 管理实例以保护 API。This scenario shows you how to configure your Azure API Management instance to protect an API. 我们会将 OAuth 2.0 协议、Azure AD B2C 与 API 管理一起使用,以使用 EasyAuth 保护 Azure Functions 后端。We'll use the OAuth 2.0 protocol with Azure AD B2C, alongside API Management to secure an Azure Functions backend using EasyAuth.

目的Aims

我们将了解如何通过 Azure Functions 和 Azure AD B2C 在简化方案中使用 API 管理。We're going to see how API Management can be used in a simplified scenario with Azure Functions and Azure AD B2C. 需要创建一个调用 API 的 JavaScript (JS) 应用,该应用使用 Azure AD B2C 使用户登录。You will create a JavaScript (JS) app calling an API, that signs in users with Azure AD B2C. 然后将使用 API 管理的 validate-jwt 策略功能来保护后端 API。Then you'll use API Management's validate-jwt policy features to protect the Backend API.

为进行深层防御,随后将使用 EasyAuth 在后端 API 内部再次验证该令牌。For defense in depth, we then use EasyAuth to validate the token again inside the back-end API.

先决条件Prerequisites

若要执行本文中的步骤,必须提供:To follow the steps in this article, you must have:

  • 用于托管前端 JS 单页应用的 Azure (StorageV2) 常规用途 V2 存储帐户An Azure (StorageV2) General Purpose V2 Storage Account to host the frontend JS Single Page App
  • 一个 Azure API 管理实例An Azure API Management instance
  • 用于托管调用 API 的空的 Azure 函数应用(在 Windows 消耗计划中运行 V2 .NET Core 运行时)An empty Azure Function app (running the V2 .NET Core runtime, on a Windows Consumption Plan) to host the called API
  • 链接到订阅的 Azure AD B2C 租户An Azure AD B2C tenant, linked to a subscription

尽管在实际情况下,会在生产工作负载中使用同一区域中的资源,但是在本操作指南文章中,部署的区域并不重要。Although in practice you would use resources in the same region in production workloads, for this how-to article the region of deployment isn't important.

概述Overview

下面介绍了所使用的组件以及此过程完成后组件之间的流。Here is an illustration of the components in use and the flow between them once this process is complete. 正在使用的组件和流Components in use and flow

下面是简要的步骤概述:Here is a quick overview of the steps:

  1. 创建具有范围的 Azure AD B2C 调用(前端、API 管理)和 API 应用程序,并授予 API 访问权限Create the Azure AD B2C Calling (Frontend, API Management) and API Applications with scopes and grant API Access
  2. 创建注册或登录策略,以允许用户使用 Azure AD B2C 进行登录Create the sign up or sign in policies to allow users to sign in with Azure AD B2C
  3. 使用新的 Azure AD B2C 客户端 ID 和密钥配置 API 管理,以在开发人员控制台中启用 OAuth2 用户授权Configure API Management with the new Azure AD B2C Client IDs and keys to Enable OAuth2 user authorization in the Developer Console
  4. 生成函数 APIBuild the Function API
  5. 配置函数 API 以使用新的 Azure AD B2C 客户端 ID 和密钥启用 EasyAuth 并锁定到 APIM VIPConfigure the Function API to enable EasyAuth with the new Azure AD B2C Client ID’s and Keys and lock down to APIM VIP
  6. 在 API 管理中生成 API 定义Build the API Definition in API Management
  7. 为 API 管理 API 配置设置 Oauth2Set up Oauth2 for the API Management API configuration
  8. 设置 CORS 策略,并添加 validate-jwt 策略以验证每个传入请求的 OAuth 令牌 Set up the CORS policy and add the validate-jwt policy to validate the OAuth token for every incoming request
  9. 生成调用应用程序以使用 APIBuild the calling application to consume the API
  10. 上传 JS SPA 示例Upload the JS SPA Sample
  11. 使用新的 Azure AD B2C 客户端 ID 和密钥配置示例 JS 客户端应用Configure the Sample JS Client App with the new Azure AD B2C Client ID’s and keys
  12. 测试客户端应用程序Test the Client Application

配置 Azure AD B2CConfigure Azure AD B2C

在门户中打开“Azure AD B2C”边栏选项卡,然后执行以下步骤。Open the Azure AD B2C blade in the portal and do the following steps.

  1. 选择“应用程序”选项卡Select the Applications tab
  2. 单击“添加”按钮,针对以下目的创建三个应用程序Click the 'Add' button and create three applications for the following purposes
    • 前端客户端。The Frontend Client.
    • 后端函数 API。The Backend Function API.
    • [可选] API 管理开发人员门户(除非在消耗层中运行 Azure API 管理,稍后将对此方案进行详细介绍)。[Optional] The API Management developer portal (unless you're running Azure API Management in the consumption tier, more on this scenario later).
  3. 为所有 3 个应用程序设置 WebApp/Web API,并仅针对前端客户端将“允许隐式流”设置为“是”。Set WebApp / Web API for all 3 applications and set 'Allow Implicit flow' to yes for only the Frontend Client.
  4. 现在,设置应用 ID URI,选择与正在创建的服务相关的唯一内容。Now set the App ID URI, choose something unique and relevant to the service being created.
  5. 暂时使用回复 URL 的占位符(如 https://localhost ),稍后将更新这些 URL。Use placeholders for the reply urls for now such as https://localhost, we’ll update those urls later.
  6. 单击“创建”,然后分别为上述三个应用重复步骤 2-5,记录 AppID URI、名称和应用程序 ID,以供以后用于这三个应用。Click 'Create', then repeat steps 2-5 for each of the three apps above, recording the AppID URI, name, and Application ID for later use for all three apps.
  7. 从应用程序列表中打开 API 管理开发人员门户应用程序,选择“密钥”选项卡(在“常规”下),然后单击“生成密钥”以生成身份验证密钥Open the API Management Developer Portal Application from the list of applications and select the Keys tab (under General) then click 'Generate Key' to generate an auth key
  8. 单击“保存”后,在安全位置记录密钥以供以后使用,请注意,此位置是查看和复制此密钥的唯一机会。Upon clicking save, record the key somewhere safe for later use - note that this place is the ONLY chance will you get to view and copy this key.
  9. 现在,选择“已发布的范围”选项卡(在“API 访问”下)Now select the Published Scopes Tab (Under API Access)
  10. 创建和命名函数 API 的范围并记录范围和已填充的完整范围值,然后单击“保存”。Create and name a scope for your Function API and record the Scope and populated Full Scope Value, then click 'Save'.

    备注

    Azure AD B2C 范围是 API 中的有效权限,其他应用程序可以从其应用程序的“API 访问权限”边栏选项卡请求该 API 的访问权限,因此,只需为所调用的 API 创建应用程序权限。Azure AD B2C scopes are effectively permissions within your API that other applications can request access to via the API access blade from their applications, effectively you just created application permissions for your called API.

  11. 打开另外两个应用程序,然后在“API 访问”选项卡下查看。Open the other two applications and then look under the API Access tab.
  12. 向它们授予对后端 API 范围的访问权限和已有内容的默认访问权限(“访问用户的配置文件”)。Grant them access to the backend API scope and the default one that was already there ("Access the user's profile").
  13. 通过选择“常规”下的“密钥”选项卡,为它们生成一个密钥,以生成身份验证密钥并将这些密钥记录到安全的位置,以供以后使用。Generate them a key each by selecting the Keys tab under 'General' to generate an auth key and record those keys somewhere safe for later.

创建“注册或登录”用户流Create a "Sign up or Sign in" user flow

  1. 返回到“Azure AD B2C”边栏选项卡的根(或“概述”)Return to the root (Or 'Overview') of the Azure AD B2C Blade

  2. 然后选择“用户流(策略)”,单击“新建用户流”Then select “User Flows (Policies)” and click "New user flow"

  3. 选择“注册和登录”用户流类型Choose the 'Sign up and sign in' user flow type

  4. 为策略指定一个名称并记录,以供以后使用。Give the policy a name and record it for later.

  5. 然后在“标识提供者”下,选中“用户 ID 注册”(可能为“电子邮件注册”),然后单击“确定”。Then Under 'Identity providers', then check 'User ID sign up' (this may say 'Email sign up') and click OK.

  6. 在“用户属性和声明”下,单击“显示更多…”,然后选择希望用户输入并在令牌中返回的声明选项。Under 'User Attributes and claims', click 'Show More...' then choose the claim options that you want your users to enter and have returned in the token. 至少选中要收集和返回的“显示名称”和“电子邮件地址”,然后依次单击“确定”和“创建”。Check at least 'Display Name' and 'Email Address' to collect and return, and click 'OK', then click 'Create'.

  7. 选择在列表中创建的策略,然后单击“运行用户流”按钮。Select the policy that you created in the list, then click the 'Run user flow' button.

  8. 此操作将打开“运行用户流”边栏选项卡,选择前端应用程序,然后记录“选择域”下拉列表下显示的 b2clogin.cn 域的地址。This action will open the run user flow blade, select the frontend application, then record the address of the b2clogin.cn domain that's shown under the dropdown for 'Select domain'.

  9. 单击顶部的链接以打开“已知 openid 配置终结点",并记录 authorization_endpoint 和 token_endpoint 值以及链接本身的值作为已知 openid 配置终结点。Click on the link at the top to open the 'well-known openid configuration endpoint', and record the authorization_endpoint and token_endpoint values as well of the value of the link itself as the well-known openid configuration endpoint.

    备注

    使用 B2C 策略可以公开 Azure AD B2C 登录终结点,使其能够捕获不同的数据组件,并以不同的方式使用户登录。B2C Policies allow you to expose the Azure AD B2C login endpoints to be able to capture different data components and sign in users in different ways. 在本示例中,我们配置了注册或登录终结点,该终结点公开了已知配置终结点,特别是在 URL 中通过 p= 参数标识了我们创建的策略。In this case we configured a sign up or sign in endpoint, which exposed a well-known configuration endpoint, specifically our created policy was identified in the URL by the p= parameter.

    完成此操作后,就有了一个功能性的企业对消费者标识平台,它可将用户登录到多个应用程序。Once this is done - you now have a functional Business to Consumer identity platform that will sign users into multiple applications. 如果需要,可以在此处单击“运行用户流”按钮(以完成注册或登录过程)并了解它的实际行为,但最终的重定向步骤会失败,因为该应用尚未部署。If you want to you can click the 'Run user flow' button here (to go through the sign up or sign in process) and get a feel for what it will do in practice, but the redirection step at the end will fail as the app has not yet been deployed.

生成函数 APIBuild the function API

  1. 切换回 Azure 门户中的标准 Azure AD 租户,以便可以重新配置订阅中的项Switch back to your standard Azure AD tenant in the Azure portal so we can configure items in your subscription again

  2. 转到 Azure 门户的“函数应用”边栏选项卡,打开空的函数应用,然后通过快速入门创建新的门户内“Webhook + API”函数。Go to the Function Apps blade of the Azure portal, open your empty function app, then create a new In-Portal 'Webhook + API' function via the quickstart.

  3. 将下面的示例代码粘贴到 Run.csx 中,替换显示的现有代码。Paste the sample code from below into Run.csx over the existing code that appears.

    
    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”,其中包含一些动态数据(日期和时间)。The c# script function code you just pasted simply logs a line to the functions logs, and returns the text "Hello World" with some dynamic data (the date and time).

  4. 选择左侧边栏选项卡中的“集成”,然后选择窗格右上角的“高级编辑器”。Select “Integrate” from the left-hand blade, then select ‘Advanced Editor’ in the top-right-hand corner of the pane.

  5. 将下面的示例代码粘贴到现有 json 上。Paste the sample code below over the existing json.

    {
       "bindings": [
        {
         "authLevel": "function",
         "name": "req",
         "type": "httpTrigger",
         "direction": "in",
         "methods": [
            "get"
         ],
         "route": "hello"
        },
        {
          "name": "$return",
          "type": "http",
          "direction": "out"
        }
      ]
    }
    
  6. 切换回“HttpTrigger1”选项卡,单击“获取函数 URL”,然后复制显示的 URL。Switch back to the HttpTrigger1 tab, click 'Get Function URL', then copy the URL that appears.

    备注

    刚创建的绑定只是指示 Functions 对刚刚复制的 URL 的匿名 http GET 请求做出响应。The bindings you just created simply tell Functions to respond on anonymous http GET requests to the URL you just copied. (https://yourfunctionappname.chinacloudsites.cn/api/hello?code=secretkey) 现在,我们有一个可缩放的无服务器 https API,它能够返回非常简单的有效负载。(https://yourfunctionappname.chinacloudsites.cn/api/hello?code=secretkey) Now we have a scalable serverless https API, that is capable of returning a very simple payload. 现在可以使用上述 URL 测试从 Web 浏览器对此 API 的调用,还可以去除 URL 的 ?code=secret 部分,并证明 Azure Functions 将返回 401 错误。You can now test calling this API from a web browser using the URL above, you can also strip the ?code=secret portion of the URL and prove that Azure Functions will return a 401 error.

配置和保护函数 APIConfigure and secure the function API

  1. 需要配置函数应用中的两个额外区域(身份验证和网络限制)。Two extra areas in the function app need to be configured (Auth and Network Restrictions).

  2. 首先,让我们配置身份验证/授权,然后单击函数应用的名称(<Z> 函数图标旁边)以显示概述页面。Firstly Let's configure Authentication / Authorization, so click on the name of the function app (next to the <Z> functions icon) to show the overview page.

  3. 接下来,选择“平台功能”选项卡,然后选择“身份验证/授权”。Next Select the 'Platform features' tab and select 'Authentication / Authorization'.

  4. 启用“应用服务身份验证”功能。Turn on the App Service Authentication feature.

  5. 在“身份验证提供程序”下,选择“Azure Active Directory”,然后从“管理模式”开关中选择“高级”。Under 'Authentication Providers' choose ‘Azure Active Directory’, and choose ‘Advanced’ from the Management Mode switch.

  6. 将后端函数 API 的应用程序 ID 从 Azure AD B2C 粘贴到“客户端 ID”框中Paste the Backend Function API's application ID (from Azure AD B2C into the ‘Client ID’ box)

  7. 将已知 open-id 配置终结点从注册或登录策略粘贴到“颁发者 URL”框中(前面已记录此配置)。Paste the Well-known open-id configuration endpoint from the sign up or sign in policy into the Issuer URL box (we recorded this configuration earlier).

  8. 选择“确定”。Select OK.

  9. 将“请求未经身份验证时需执行的操作”下拉列表设置为“使用 Azure Active Directory 登录”,然后单击“保存”。Set the Action to take when request is not authenticated dropdown to "Log in with Azure Active Directory", then click Save.

    备注

    现在,如果未提供正确的密钥,函数 API 则会部署并应引发 401 响应,还应在出现有效请求时返回数据。Now your Function API is deployed and should throw 401 responses if the correct key is not supplied, and should return data when a valid request is presented. 通过配置“使用 Azure AD 登录”选项,已在 EasyAuth 中添加了其他深层防御安全性,以处理未经身份验证的请求。You added additional defense-in-depth security in EasyAuth by configuring the 'Login With Azure AD' option to handle unauthenticated requests. 请注意,这会更改后端函数应用和前端 SPA 之间未经授权的请求行为,因为 EasyAuth 将发出到 AAD 的 302 重定向,而不是 401“未授权”响应,我们将在稍后使用 API 管理来更正此问题。Be aware that this will change the unauthorized request behavior between the Backend Function App and Frontend SPA as EasyAuth will issue a 302 redirect to AAD instead of a 401 Not Authorized response, we will correct this by using API Management later. 我们仍未应用 IP 安全性,如果拥有有效的密钥和 OAuth2 令牌,则任何人都可以从任何位置调用它,理想情况下,我们希望强制所有请求都通过 API 管理传入。We still have no IP security applied, if you have a valid key and OAuth2 token, anyone can call this from anywhere - ideally we want to force all requests to come via API Management. 如果使用的是 API 管理消耗层,将无法通过 VIP 执行此锁定,因为没有用于该层的专用静态 IP,将需要依赖通过共享机密功能密钥锁定 API 调用的方法,因此无法执行步骤 11-13。If you are using the API Management consumption tier, you will not be able to perform this lockdown by VIP as there is no dedicated static IP for that tier, you will need to rely on the method of locking down your API calls via the shared secret function key, so steps 11-13 will not be possible.

  10. 关闭“身份验证/授权”边栏选项卡Close the 'Authentication / Authorization' blade

  11. 选择“网络”,然后选择“访问限制”Select 'Networking' and then select 'Access Restrictions'

  12. 接下来,将允许的函数应用 IP 锁定到 API 管理实例 VIP。Next, lock down the allowed function app IPs to the API Management instance VIP. 此 VIP 显示在门户的“API 管理 - 概述”部分中。This VIP is shown in the API management - overview section of the portal.

  13. 如果要继续与函数门户交互,并执行以下可选步骤,还应在此处添加自己的公共 IP 地址或 CIDR 范围。If you want to continue to interact with the functions portal, and to carry out the optional steps below, you should add your own public IP address or CIDR range here too.

  14. 列表中有允许条目后,Azure 会添加隐式拒绝规则以阻止所有其他地址。Once there’s an allow entry in the list, Azure adds an implicit deny rule to block all other addresses.

需要将 CIDR 格式的地址块添加到 IP 限制面板。You'll need to add CIDR formatted blocks of addresses to the IP restrictions panel. 如果需要添加单个地址(如 API 管理 VIP),则需要将其添加为 xx.xx.xx.xx 格式。When you need to add a single address such as the API Management VIP, you need to add it in the format xx.xx.xx.xx.

备注

现在,除了 API 管理或你的地址之外,应不能从任何位置调用函数 API。Now your Function API should not be callable from anywhere other than via API management, or your address.

导入函数应用定义Import the function app definition

  1. 打开“API 管理”边栏选项卡,然后打开你的实例 。Open the API Management blade, then open your instance.
  2. 从实例的“API 管理”部分中选择“API”边栏选项卡。Select the APIs Blade from the API Management section of your instance.
  3. 从“添加新 API”窗格中选择“函数应用”,然后从弹出窗口顶部选择“完整”。From the 'Add a New API' pane, choose 'Function App', then select 'Full' from the top of the popup.
  4. 单击“浏览”,选择要在其中托管 API 的函数应用,然后单击“选择”。Click Browse, choose the function app you're hosting the API inside, and click select.
  5. 为 API 提供名称和描述,以便内部使用 API 管理,并将其添加到“无限制”产品。Give the API a name and description for API Management's internal use and add it to the ‘unlimited’ Product.
  6. 请确保记录基本 URL 以供以后使用,然后单击“创建”。Make sure you record the base URL for later use and then click create.

为 API 管理配置 Oauth2Configure Oauth2 for API Management

  1. 接下来,从“安全”选项卡中选择“Oauth 2.0”边栏选项卡,然后单击“添加”Next, Select the Oauth 2.0 blade from the Security Tab, and click 'Add'
  2. 为添加的 Oauth 终结点指定“显示名称”和“说明”的值(这些值将在下一步中显示为 Oauth2 终结点) 。Give values for Display Name and Description for the added Oauth Endpoint (these values will show up in the next step as an Oauth2 endpoint).
  3. 可以在“客户端注册页 URL”中输入任何值,因为不会使用此值。You can enter any value in the Client registration page URL, as this value won't be used.
  4. 勾选“隐式身份验证”授权类型,并继续选中“授权代码”授权类型。Check the Implicit Auth Grant type and leave the the Authorization code grant type checked.
  5. 转到“授权”和“令牌”终结点字段,并输入先前从已知配置 xml 文档中捕获的值 。Move to the Authorization and Token endpoint fields, and enter the values you captured from the well-known configuration xml document earlier.
  6. 向下滚动,并使用 Azure AD B2C 应用注册中的后端函数 API 客户端 ID 填充名为“资源”的附加正文参数Scroll down and populate an Additional body parameter called 'resource' with the Backend Function API client ID from the Azure AD B2C App registration
  7. 选择“客户端凭据”,将“客户端 ID”设置为开发人员控制台应用的应用 ID,如果使用消耗 API 管理模式,请跳过此步骤。Select 'Client credentials', set the Client ID to the Developer console app's app ID - skip this step if using the consumption API Management model.
  8. 将“客户端密码”设置为之前记录的密钥,如果使用消耗 API 管理模式,请跳过此步骤。Set the Client Secret to the key you recorded earlier - skip this step if using the consumption API Management model.
  9. 最后,记录 API 管理的身份验证代码授权的 redirect_uri 以供以后使用。Lastly, now record the redirect_uri of the auth code grant from API Management for later use.

为 API 设置 Oauth2Set up Oauth2 for your API

  1. API 将显示在门户左侧的“所有 API”部分下,通过单击打开 API。Your API will appear on the left-hand side of the portal under the 'All APIs' section, open your API by clicking on it.

  2. 选择“设置”选项卡。Select the 'Settings' Tab.

  3. 通过从“用户授权”单选按钮中选择“Oauth 2.0”来更新设置。Update your settings by selecting “Oauth 2.0” from the user authorization radio button.

  4. 选择前面定义的 Oauth 服务器。Select the Oauth server that you defined earlier.

  5. 选中“替代范围”复选框,然后输入之前为后端 API 调用记录的范围。Check the ‘Override scope’ checkbox and enter the scope you recorded for the backend API call earlier on.

    备注

    现在,我们有一个 API 管理实例,该实例知道如何从 Azure AD B2C 获取访问令牌来授权请求并了解我们的 Oauth2 Azure Active Directory B2C 配置。Now we have an API Management instance that knows how to get access tokens from Azure AD B2C to authorize requests and understands our Oauth2 Azure Active Directory B2C configuration.

设置 CORS 和 validate-jwt 策略 Set up the CORS and validate-jwt policies

无论使用何种 APIM 层,都应遵循以下部分。The following sections should be followed regardless of the APIM tier being used.

  1. 切换回“设计”选项卡,选择“所有 API”,然后单击代码视图按钮以显示策略编辑器。Switch back to the design tab and choose “All APIs”, then click the code view button to show the policy editor.

  2. 编辑入站部分并粘贴以下 xml,使其类似于以下内容。Edit the inbound section and paste the below xml so it reads like the following.

    <inbound>
       <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid.">
          <openid-config url="https://tenant.b2clogin.cn/tenant.partner.onmschina.cn/v2.0/.well-known/openid-configuration?p=B2C_1_MyDefaultPolicy" />
          <required-claims>
             <claim name="aud">
                <value>your-backend-api-application-client-id</value>
             </claim>
          </required-claims>
       </validate-jwt>
       <cors>
          <allowed-origins>
             <origin>*</origin>
          </allowed-origins>
          <allowed-methods>
            <method>GET</method>
          </allowed-methods>
          <allowed-headers>
             <header>*</header>
          </allowed-headers>
          <expose-headers>
            <header>*</header>
          </expose-headers>
        </cors>
    </inbound>
    
  3. 编辑 openid-config URL,使其与注册或登录策略的已知 Azure AD B2C 终结点匹配。Edit the openid-config url to match your well-known Azure AD B2C endpoint for the sign up or sign in policy.

  4. 编辑声明值以匹配有效的应用程序 ID (也称为后端 API 应用程序的客户端 ID)并保存。Edit the claim value to match the valid application ID, also known as a client ID for the backend API application and save.

    备注

    现在,API 管理可以响应对 JS SPA 应用的跨源请求,并对在将请求转发到函数 API 之前要传递的 JWT 身份验证令牌执行限制、速率限制和预验证。Now API management is able respond to cross origin requests to JS SPA apps, and it will perform throttling, rate-limiting and pre-validation of the JWT auth token being passed BEFORE forwarding the request on to the Function API.

    备注

    以下部分是可选的,不适用于不支持开发人员门户的“消耗”层。The following section is optional and does not apply to the Consumption tier, which does not support the developer portal. 如果不打算使用开发人员门户,或者因使用消耗层而无法使用开发人员门户,请跳过此步骤并直接跳到“生成 JavaScript SPA 以使用 API”If you do not intend to use the developer portal, or cannot use it since you are using the Consumption tier, please skip this step and jump straight to "Build the JavaScript SPA to consume the API".

[可选] 配置开发人员门户[Optional] Configure the developer portal

  1. 打开“Azure AD B2C”边栏选项卡,然后导航到开发人员门户的应用程序注册Open the Azure AD B2C blade and navigate to the application registration for the Developer Portal

  2. 将“回复 URL”条目设置为之前在 API 管理中配置身份验证代码授权的 redirect_uri 时记下的条目。Set the 'Reply URL' entry to the one you noted down when you configured the redirect_uri of the auth code grant in API Management earlier.

    Echo API 中启用 OAuth 2.0 用户授权后,开发人员控制台在调用 API 之前,将会为用户获取访问令牌。Now that the OAuth 2.0 user authorization is enabled on the Echo API, the Developer Console obtains an access token for the user, before calling the API.

  3. 在开发人员门户中,浏览到 Echo API 下的任一操作,然后选择“试用”以转到开发人员控制台。Browse to any operation under the Echo API in the developer portal, and select Try it to bring you to the Developer Console.

  4. 可以看到,“授权”部分出现了一个与刚刚添加的授权服务器对应的新项。Note a new item in the Authorization section, corresponding to the authorization server you just added.

  5. 从授权下拉列表中选择“授权代码”。系统会提示登录到 Azure AD 租户。Select Authorization code from the authorization drop-down list, and you're prompted to sign in to the Azure AD tenant. 如果已使用帐户登录,则可能不会出现提示。If you're already signed in with the account, you might not be prompted.

  6. 成功登录后,会将一个 Authorization: Bearer 标头添加到请求,该标头包含从 Azure AD B2C 获取的以 Base64 编码的访问令牌。After successful sign-in, an Authorization: Bearer header is added to the request, with an access token from Azure AD B2C encoded in Base64.

  7. 选择“发送”,然后即可成功调用 API。Select Send and you can call the API successfully.

    备注

    现在,API 管理能够获取开发人员门户的令牌来测试 API,并能够理解其定义以及在开发人员门户中呈现适当的测试页。Now API management is able to acquire tokens for the developer portal to test your API and is able to understand it's definition and render the appropriate test page in the dev portal.

  8. 在 API 管理门户的“概述”边栏选项卡中,单击“开发人员门户”,以 API 的管理员身份登录。From the overview blade of the API Management portal, click 'Developer Portal' to sign in as an administrator of the API.

  9. 此时,你和你的 API 的其他选定使用者可以从控制台对其进行测试和调用。Here, you and other selected consumers of your API can test and call them from a console.

  10. 选择“产品”,选择“无限制”,然后选择前面创建的 API,单击“试用”Select ‘Products’, then choose ‘Unlimited’, then choose the API we created earlier and click ‘TRY IT’

  11. 取消隐藏 API 订阅密钥,并将其与稍后需要的请求 URL 一起复制到安全位置。Unhide the API subscription key, and copy it somewhere safe along with the request url that you'll need later.

  12. 此外,从“oauth 身份验证”下拉列表中选择“隐式”,此时可能需要通过弹出窗口进行身份验证。Also select Implicit, from the oauth auth dropdown and you may have to authenticate here with a popup.

  13. 单击“发送”,如果一切正常,函数应用应通过 API 管理回复问候消息,其中包括 200 正常消息和一些 JSON。Click ‘Send’ and if all is well, your Function App should respond back with a hello message via API management with a 200 OK message and some JSON.

    备注

    恭喜,Azure AD B2C、API 管理和 Azure Functions 现可协同工作以发布、保护和使用 API。Congratulations, you now have Azure AD B2C, API Management and Azure Functions working together to publish, secure AND consume an API. 你可能已注意到,使用此方法实际上对 API 进行了两次保护,一次通过 API 管理 Ocp-Subscription-Key 标头,一次通过授权:Bearer JWT。You might have noticed that the API is in fact secured twice using this method, once with the API Management Ocp-Subscription-Key Header, and once with the Authorization: Bearer JWT. 你是正确的,因为此示例是一个 JavaScript 单页应用程序,因此,我们只使用 API 管理密钥进行速率限制和计费调用。You would be correct, as this example is a JavaScript Single Page Application, we use the API Management Key only for rate-limiting and billing calls. 实际的授权和身份验证由 Azure AD B2C 处理,并封装在 JWT 中,此过程通过 API 管理和 Azure Functions 进行两次验证。The actual Authorization and Authentication is handled by Azure AD B2C, and is encapsulated in the JWT, which gets validated twice, once by API Management, and then by Azure Functions.

生成 JavaScript SPA 以使用 APIBuild the JavaScript SPA to consume the API

  1. 在 Azure 门户中打开“存储帐户”边栏选项卡Open the storage accounts blade in the Azure portal

  2. 选择所创建的帐户,然后从“设置”部分选择“静态网站”边栏选项卡(如果看不到“静态网站”选项,请检查是否已创建 V2 帐户)。Select the account you created and select the 'Static Website' blade from the Settings section (if you don't see a 'Static Website' option, check you created a V2 account).

  3. 将静态 Web 托管功能设置为“已启用”,将索引文档名称设置为“index.html”,然后单击“保存”。Set the static web hosting feature to 'enabled', and set the index document name to 'index.html', then click 'save'.

  4. 记下主终结点的内容,因为此位置是托管前端站点的位置。Note down the contents of the Primary Endpoint, as this location is where the frontend site will be hosted.

    备注

    可以使用 Azure Blob 存储 + CDN 重写或 Azure 应用服务,但 Blob 存储的静态网站托管功能为我们提供了一个默认容器来为 Azure 存储中的静态 Web 内容/html/js/css 提供服务,并将推断默认页面,无需我们执行任何工作。You could use either Azure Blob Storage + CDN rewrite, or Azure App Service - but Blob Storage's Static Website hosting feature gives us a default container to serve static web content / html / js / css from Azure Storage and will infer a default page for us for zero work.

上传 JS SPA 示例Upload the JS SPA sample

  1. 依然是在“存储帐户”边栏选项卡中,从“Blob 服务”部分选择“Blob”边栏选项卡,并单击右侧窗格中显示的 $web 容器。Still in the storage account blade, select the 'Blobs' blade from the Blob Service section and click on the $web container that appears in the right-hand pane.

  2. 将以下代码作为 index.html 保存到计算机上的本地文件中,然后将文件 index.html 上传到 $web 容器。Save the code below to a file locally on your machine as index.html and then upload the file index.html to the $web container.

    <!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">
         <title>Sample JS SPA</title>
         <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    </head>
    <body>
         <div class="container-fluid">
             <div class="row">
                 <div class="col-md-12">
                     <nav class="navbar navbar-expand-lg navbar-light bg-light navbar-dark bg-dark">
                         <a class="navbar-brand" href="#">Sample Code</a>
                         <ul class="navbar-nav ml-md-auto">
                             <li class="nav-item dropdown">
                                 <a class="btn btn-large btn-success" onClick="login()">Sign In</a>
                             </li>
                         </ul>
                     </nav>
                 </div>
             </div>
             <div class="row">
                 <div class="col-md-12">
                     <div class="jumbotron">
                         <h2>
                     <div id="message">Hello, world!</div>
                     </h2>
                         <p>
                             <a class="btn btn-primary btn-large" onClick="GetAPIData()">Call API</a>
                         </p>
                     </div>
                 </div>
             </div>
         </div>
         <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
         <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
         <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
         <script src="https://secure.aadcdn.partner.microsoftonline-p.cn/lib/1.0.0/js/msal.js"></script>
         <script lang="javascript">
             var applicationConfig = {
                 clientID: "clientidgoeshere",
                 authority: "https://tenant.b2clogin.cn/tfp/tenant/policy",
                 b2cScopes: ["https://tenant/app/scope"],
                 webApi: 'http://functionurl',
                 subKey: 'apimkeygoeshere'
             };
             var msalConfig = {
                 auth: {
                         clientId: applicationConfig.clientID, 
                         authority:  applicationConfig.authority,
                         validateAuthority: false
                 },
                 cache: {
                         cacheLocation: "localStorage",
                         storeAuthStateInCookie: true
                 }
             };
             var clientApplication = new Msal.UserAgentApplication(msalConfig);
             function login() {
                 var loginRequest = {
                     scopes: applicationConfig.b2cScopes
                 };
                 clientApplication.loginPopup(loginRequest).then(function (loginResponse) {
                     var tokenRequest = {
                         scopes: applicationConfig.b2cScopes
                     };
                     clientApplication.acquireTokenSilent(tokenRequest).then(function (tokenResponse) {
                         document.getElementById("signinbtn").innerHTML = "Logged in as: " + clientApplication.account.name;
                         document.getElementById("callapibtn").hidden = false
                         }).catch(function (error) {
                             clientApplication.acquireTokenPopup(tokenRequest).then(function (tokenResponse) {
                                 }).catch (function (error) {
                                     console.log("Error acquiring the popup:\n" + error);
                                 });
                         })
                     }).catch (function (error) {
                         console.log("Error during login:\n" + error);
                 });
             }
             function GetAPIData() {
                 var tokenRequest = {
                     scopes: applicationConfig.b2cScopes
                 }
                 clientApplication.acquireTokenSilent(tokenRequest).then(function (tokenResponse) {
                     callApiWithAccessToken(tokenResponse.accessToken);
                     }).catch(function (error) {
                         clientApplication.acquireTokenPopup(tokenRequest).then(function (tokenResponse) {
                             callApiWithAccessToken(tokenResponse.accessToken);
    
                         }).catch(function (error) {
                             console.log("Error acquiring the access token to call the Web api:\n" + error);
                         });
                     })
             }
             function callApiWithAccessToken(token)
             {
                 console.log("calling "  + applicationConfig.webApi +  " with " + token);
                     // Make the api call here
                 $.ajax({
                     type: "get",
                     headers: {'Authorization': 'Bearer ' + token, 'Ocp-Apim-Subscription-Key': applicationConfig.subKey},                   url: applicationConfig.webApi
                 }
                 ).done(function (body) {
                     document.getElementById("message").innerHTML = "The API Said " + body;
                 });
             }
         </script>
     </body>
    </html>
    
    
  3. 浏览到之前在上一部分存储的静态网站主终结点。Browse to the Static Website Primary Endpoint you stored earlier in the last section.

    备注

    恭喜,你刚刚将 JavaScript 单页应用部署到了 Azure 存储。由于我们尚未使用你的 API 密钥配置 JS 应用或使用 Azure AD B2C 详细信息配置 JS 应用,如果你打开该页面,此页面将不起作用。Congratulations, you just deployed a JavaScript Single Page App to Azure Storage Since we haven’t configured the JS app with your keys for the api or configured the JS app with your Azure AD B2C details yet - the page will not work yet if you open it.

为 Azure AD B2C 配置 JS SPAConfigure the JS SPA for Azure AD B2C

  1. 现在,我们已经了解了所有内容的所在位置:我们可以通过适当的 API 管理 API 地址和正确的 Azure AD B2C 应用程序/客户端 ID 配置 SPANow we know where everything is: we can configure the SPA with the appropriate API Management API address and the correct Azure AD B2C application / client IDs

  2. 返回 Azure 门户存储边栏选项卡,单击 index.html,然后选择“编辑 Blob”Go back to the Azure portal storage blade and click on index.html, then choose ‘Edit Blob’

  3. 更新身份验证详细信息,使其与先前在 B2C 中注册的前端应用程序相匹配,请注意“b2cScopes”值用于 API 后端。Update the auth details to match your front-end application you registered in B2C earlier, noting that the 'b2cScopes' values are for the API backend.

  4. 可在 API 管理测试窗格中找到用于 API 操作的 webApi 密钥和 API URL。The webApi key and api url can be found in the API Management test pane for the API operation.

  5. 创建 APIM 订阅密钥:前往 API 管理,返回“API 管理”边栏选项卡,选择“订阅”,然后单击“添加订阅”并保存记录。Create An APIM subscription key by heading to the API Management back to the API Management blade, selecting 'Subscriptions', and clicking 'Add Subscription' then saving the record. 单击创建的行旁边的省略号 (...) 将显示密钥,以便可以复制主密钥。Clicking the Ellipsis (...) next to the created row will allow you to show the keys so you can copy the primary key.

  6. 它应该类似于以下代码:-It should look something like the below code:-

    var applicationConfig =
       clientID: "{aadb2c-clientid-goeshere}",
       authority: "https://{tenant}.b2clogin.cn/{tenant}/{policy}",
       b2cScopes: ["https://{tenant}/{app}/{scope}"], 
       webApi: 'http://{apim-url-for-your-function}',
       subKey: '{apim-subscription-key-goes-here}'
    };
    
  7. 点击“保存”(Save)Click Save

设置 Azure AD B2C 前端应用的重定向 URISet the redirect URIs for the Azure AD B2C frontend app

  1. 打开“Azure AD B2C”边栏选项卡,然后导航到 JavaScript 前端应用程序的应用程序注册Open the Azure AD B2C blade and navigate to the application registration for the JavaScript Frontend Application

  2. 将重定向 URL 设置为之前设置静态网站主终结点时记下的 URLSet the redirect URL to the one you noted down when you previously set up the static website primary endpoint above

    备注

    此配置将导致前端应用程序的客户端收到具有 Azure AD B2C 中的相应声明的访问令牌。This configuration will result in a client of the frontend application receiving an access token with appropriate claims from Azure AD B2C. SPA 将能够将此作为持有者令牌添加到后端 API 调用的 https 标头中。The SPA will be able to add this as a bearer token in the https header in the call to the backend API. 在将请求传递到接收 Azure 函数 API 之前,API 管理将通过订阅者密钥预先验证令牌以及对终结点的速率限制调用。API Management will pre-validate the token, rate-limit calls to the endpoint by the subscriber key, before passing through the request to the receiving Azure Function API. SPA 将在浏览器中呈现响应。The SPA will render the response in the browser.

    恭喜,你已配置了可完美协作的 Azure AD B2C、Azure API 管理、Azure Functions、Azure 应用服务授权!Congratulations, you’ve configured Azure AD B2C, Azure API Management, Azure Functions, Azure App Service Authorization to work in perfect harmony!

    备注

    现在,我们有了一个具有简单安全的 API 的简单应用,我们来对其进行测试。Now we have a simple app with a simple secured API, let's test it.

测试客户端应用程序Test the client application

  1. 打开之前创建的存储帐户中记下的示例应用 URLOpen the sample app URL that you noted down from the storage account you created earlier
  2. 单击右上角的“登录”,单击后将弹出 Azure AD B2C 注册或登录配置文件。Click “Sign In” in the top-right-hand corner, this click will pop up your Azure AD B2C sign up or sign in profile.
  3. 将从 JWT 填充屏幕上“登录身份”部分中的发布登录。Post Sign in the "Logged in as" section of the screen will be populated from your JWT.
  4. 现在,请单击“调用 Web API”,此时应通过从安全的 API 发回的值更新页面。Now Click "Call Web Api", and you the page should update with the values sent back from your secured API.

大功告成And we're done

可以调整和编辑上述步骤,以通过 API 管理实现 Azure AD B2C 的多种不同用途。The steps above can be adapted and edited to allow many different uses of Azure AD B2C with API Management.

后续步骤Next steps