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

教程:在 Azure 应用服务中创建安全的 N 层应用

许多应用程序具有多个组件。 例如,你可能有一个可公开访问的前端,该前端连接到后端 API 或 Web 应用,而后者又连接到数据库、存储帐户、密钥保管库、另一个 VM 或这些资源的组合。 此体系结构构成了一个 N 层应用程序。 重要的是,此类应用程序的体系结构必须能够最大限度地保护后端资源。

本教程介绍如何使用一个前端 Web 应用(该应用连接到另一个网络隔离的 Web 应用)来部署安全的 N 层应用程序。 使用虚拟网络集成专用终结点在 Azure 虚拟网络中隔离所有流量。 有关包括其他方案的更全面指导,请参阅:

方案体系结构

下图显示了本教程稍后将会创建的体系结构。

Architecture diagram of an n-tier App Service.

  • 虚拟网络包含两个子网,一个子网与前端 Web 应用集成,另一个子网具有后端 Web 应用的专用终结点。 虚拟网络阻止所有入站网络流量,但与其集成的前端应用的流量除外。
  • 前端 Web 应用:已集成到虚拟网络,可从公共 Internet 对其进行访问。
  • 后端 Web 应用:只能通过虚拟网络中的专用终结点对其进行访问。
  • 专用终结点:与后端 Web 应用集成,使 Web 应用可以通过专用 IP 地址进行访问。
  • 专用 DNS 区域:允许将 DNS 名称解析为专用终结点的 IP 地址。

注意

应用服务中的“基本”层及更高层都可以使用虚拟网络集成和专用终结点。 “免费”层不支持这些功能。 使用此体系结构:

  • 发往后端应用的公共流量将被阻止。
  • 来自应用服务的出站流量将路由到虚拟网络并可以进入后端应用。
  • 应用服务能够对后端应用执行 DNS 解析。

此方案演示了应用服务中可能的 N 层方案之一。 可以使用本教程中介绍的概念来生成更复杂的 N 层应用。

学习内容:

  • 为应用服务虚拟网络集成创建虚拟网络和子网。
  • 创建专用 DNS 区域。
  • 创建专用终结点。
  • 在应用服务中配置虚拟网络集成。
  • 在应用服务中禁用基本身份验证。
  • 持续部署到锁定的后端 Web 应用。

先决条件

本教程使用 GitHub 上托管的两个示例 Node.js 应用。 如果你没有 GitHub 帐户,可以免费创建一个帐户

如果没有 Azure 订阅,请在开始之前创建一个 Azure 免费帐户

为完成此教程:

1. 创建两个 Web 应用实例

需要两个 Web 应用实例,一个用于前端,一个用于后端。 至少需要有“基本”层才能使用虚拟网络集成和专用终结点。 稍后将配置虚拟网络集成和其他配置。

  1. 创建一个资源组用于管理你在本教程中创建的所有资源。

    # Save resource group name and region as variables for convenience
    groupName=myresourcegroup
    region=eastus
    az group create --name $groupName --location $region
    
  2. 创建应用服务计划。 将 <app-service-plan-name> 替换为唯一名称。 如果需要使用不同的 SKU,请修改 --sku 参数。 切勿使用免费层,因为该 SKU 不支持所需的网络功能。

    # Save App Service plan name as a variable for convenience
    aspName=<app-service-plan-name>
    az appservice plan create --name $aspName --resource-group $groupName --is-linux --location $region --sku P1V3
    
  3. 创建 Web 应用。 将 <frontend-app-name><backend-app-name> 替换为两个全局唯一的名称(有效字符为 a-z0-9-)。 在本教程中,为你提供了示例 Node.js 应用。 如果你要使用自己的应用,请相应地更改 --runtime 参数。 运行 az webapp list-runtimes 获取可用运行时的列表。

    az webapp create --name <frontend-app-name> --resource-group $groupName --plan $aspName --runtime "NODE:18-lts"
    az webapp create --name <backend-app-name> --resource-group $groupName --plan $aspName --runtime "NODE:18-lts"
    

2. 创建网络基础结构

你将创建以下网络资源:

  • 一个虚拟网络。
  • 用于应用服务虚拟网络集成的子网。
  • 专用终结点的子网。
  • 专用 DNS 区域。
  • 一个专用终结点。
  1. 创建虚拟网络。 将 <virtual-network-name> 替换为唯一名称。

    # Save vnet name as variable for convenience
    vnetName=<virtual-network-name>
    az network vnet create --resource-group $groupName --location $region --name $vnetName --address-prefixes 10.0.0.0/16
    
  2. 为应用服务虚拟网络集成创建子网。

    az network vnet subnet create --resource-group $groupName --vnet-name $vnetName --name vnet-integration-subnet --address-prefixes 10.0.0.0/24 --delegations Microsoft.Web/serverfarms --disable-private-endpoint-network-policies false
    

    对于应用服务,建议在虚拟网络集成子网中至少包含 CIDR 块 /26/24 绰绰有余。 --delegations Microsoft.Web/serverfarms 指定将子网委托给应用服务虚拟网络集成

  3. 为专用终结点创建另一个子网。

    az network vnet subnet create --resource-group $groupName --vnet-name $vnetName --name private-endpoint-subnet --address-prefixes 10.0.1.0/24 --disable-private-endpoint-network-policies true
    

    对于专用终结点子网,必须通过将 --disable-private-endpoint-network-policies 设置为 true禁用专用终结点网络策略

  4. 创建专用 DNS 区域。

    az network private-dns zone create --resource-group $groupName --name privatelink.azurewebsites.net
    

    有关这些设置的详细信息,请参阅 Azure 专用终结点 DNS 配置

    注意

    如果你使用门户创建专用终结点,系统将自动创建专用 DNS 区域,无需你单独创建。 为了与本教程保持一致,你将使用 Azure CLI 单独创建专用 DNS 区域和专用终结点。

  5. 将专用 DNS 区域链接到虚拟网络。

    az network private-dns link vnet create --resource-group $groupName --name myDnsLink --zone-name privatelink.azurewebsites.net --virtual-network $vnetName --registration-enabled False
    
  6. 在虚拟网络的专用终结点子网中,为后端 Web 应用创建一个专用终结点。 将 <backend-app-name> 替换为后端 Web 应用名称。

    # Get backend web app resource ID
    resourceId=$(az webapp show --resource-group $groupName --name <backend-app-name> --query id --output tsv)
    az network private-endpoint create --resource-group $groupName --name myPrivateEndpoint --location $region --connection-name myConnection --private-connection-resource-id $resourceId --group-id sites --vnet-name $vnetName --subnet private-endpoint-subnet
    
  7. 使用后端 Web 应用专用终结点的 DNS 区域组将专用终结点链接到专用 DNS 区域。 更新专用终结点后,此 DNS 区域组可帮助你自动更新专用 DNS 区域。

    az network private-endpoint dns-zone-group create --resource-group $groupName --endpoint-name myPrivateEndpoint --name myZoneGroup --private-dns-zone privatelink.azurewebsites.net --zone-name privatelink.azurewebsites.net
    
  8. 为应用服务创建专用终结点时,将隐式禁用公共访问。 如果你尝试使用后端 Web 应用的默认 URL 访问该应用,则访问将被拒绝。 在浏览器中,导航到 <backend-app-name>.azurewebsites.net 以确认此行为。

    Screenshot of 403 error when trying to access backend web app directly.

    有关使用专用终结点进行应用服务访问时的限制的详细信息,请参阅 Azure 应用服务访问限制

3. 在前端 Web 应用中配置虚拟网络集成

在应用上启用虚拟网络集成。 将 <frontend-app-name> 替换为你的前端 Web 应用名称。

az webapp vnet-integration add --resource-group $groupName --name <frontend-app-name> --vnet $vnetName --subnet vnet-integration-subnet

虚拟网络集成允许出站流量直接流入虚拟网络。 默认情况下,只会将 RFC-1918 中定义的本地 IP 流量路由到虚拟网络,这正是专用终结点所需要的。 若要将所有流量路由到虚拟网络,请参阅管理虚拟网络集成路由。 若要通过虚拟网络路由 Internet 流量,也可以使用“路由所有流量”,例如通过 Azure 虚拟网络 NATAzure 防火墙

4. 启用从 Internet 到后端 Web 应用的部署

由于后端 Web 应用不可公开访问,因此必须通过使 SCM 站点可公开访问,来使持续部署工具能够访问你的应用。 主 Web 应用本身可以继续拒绝所有流量。

  1. 为后端 Web 应用启用公共访问。

    az webapp update --resource-group $groupName --name <backend-app-name> --set publicNetworkAccess=Enabled
    
  2. 为主 Web 应用设置不匹配规则操作,以拒绝所有流量。 即使常规应用访问设置指定为允许公共访问,此设置也会拒绝对主 Web 应用进行公共访问。

    az resource update --resource-group $groupName --name <backend-app-name> --namespace Microsoft.Web --resource-type sites --set properties.siteConfig.ipSecurityRestrictionsDefaultAction=Deny
    
  3. 为 SCM 站点设置不匹配规则操作以允许所有流量。

    az resource update --resource-group $groupName --name <backend-app-name> --namespace Microsoft.Web --resource-type sites --set properties.siteConfig.scmIpSecurityRestrictionsDefaultAction=Allow
    

5. 锁定 FTP 和 SCM 访问

使后端 SCM 站点可公开访问后,需要使用更高的安全性将其锁定。

  1. 为前端和后端 Web 应用禁用 FTP 访问。 将 <frontend-app-name><backend-app-name> 替换为你的应用名称。

    az resource update --resource-group $groupName --name ftp --namespace Microsoft.Web --resource-type basicPublishingCredentialsPolicies --parent sites/<frontend-app-name> --set properties.allow=false
    az resource update --resource-group $groupName --name ftp --namespace Microsoft.Web --resource-type basicPublishingCredentialsPolicies --parent sites/<backend-app-name> --set properties.allow=false
    
  2. 为这两个 Web 应用禁用以基本身份验证方法访问 WebDeploy 端口和 SCM/高级工具站点。 将 <frontend-app-name><backend-app-name> 替换为你的应用名称。

    az resource update --resource-group $groupName --name scm --namespace Microsoft.Web --resource-type basicPublishingCredentialsPolicies --parent sites/<frontend-app-name> --set properties.allow=false
    az resource update --resource-group $groupName --name scm --namespace Microsoft.Web --resource-type basicPublishingCredentialsPolicies --parent sites/<backend-app-name> --set properties.allow=false
    

通过在应用服务上禁用基本身份验证,限制只有 Microsoft Entra ID 提供支持的用户可访问 FTP 和 SCM 终结点,从而进一步保护你的应用。 有关禁用基本身份验证的详细信息,包括如何测试和监视登录,请参阅在应用服务上禁用基本身份验证

6. 使用 GitHub Actions 配置持续部署

  1. 导航到 Node.js 后端示例应用。 此应用是一个简单的 Hello World 应用。

  2. 选择 GitHub 页面右上角的“创建分支”按钮。

  3. 选择“所有者”并保留默认的存储库名称。

  4. 选择“创建分支”。

  5. Node.js 前端示例应用重复上述过程。 此应用是访问远程 URL 的基本 Web 应用。

  6. 创建服务主体。 将 <subscription-id><frontend-app-name><backend-app-name> 替换为你自己的值。

    az ad sp create-for-rbac --name "myApp" --role contributor --scopes /subscriptions/<subscription-id>/resourceGroups/$groupName/providers/Microsoft.Web/sites/<frontend-app-name> /subscriptions/<subscription-id>/resourceGroups/$groupName/providers/Microsoft.Web/sites/<backend-app-name> --sdk-auth
    

    输出是一个 JSON 对象,其中包含的角色分配凭据可提供对应用服务应用的访问权限。 请复制此 JSON 对象以便在下一步骤中使用。 这包括你的客户端机密(该机密只会显示一次)。 授予最低访问权限始终是一种很好的做法。 此示例中的范围限制为应用,而不是整个资源组。

  7. 若要将服务主体的凭据存储为 GitHub 机密,请转到 GitHub 中的某个分支示例存储库,然后转到“设置”>“安全”>“机密和变量”>“操作”。

  8. 选择“新建存储库机密”,并为以下每个值创建一个机密。 可以在前面复制的 json 输出中找到这些值。

    名称
    AZURE_APP_ID <application/client-id>
    AZURE_PASSWORD <client-secret>
    AZURE_TENANT_ID <tenant-id>
    AZURE_SUBSCRIPTION_ID <subscription-id>
  9. 对另一个分支示例存储库重复此过程。

  10. 若要使用 GitHub Actions 设置持续部署,请登录到 Azure 门户

  11. 导航到前端 Web 应用的“概述”页。

  12. 从左窗格中,选择“部署中心”。 然后选择“设置”。

  13. 在“源”框中,从 CI/CD 选项中选择“GitHub”。

    Screenshot that shows how to choose the deployment source.

  14. 若是第一次从 GitHub 进行部署,请选择“授权”并按授权提示进行操作。 若要从另一个用户的存储库进行部署,请选择“更改帐户”。

  15. 如果使用作为先决条件的一部分创建分支的 Node.js 示例应用,请为“组织”、“存储库”和“分支”使用以下设置。

    设置 Value
    组织 <your-GitHub-organization>
    存储库 nodejs-frontend
    分支 主要
  16. 选择“保存”。

  17. 对后端 Web 应用重复上述步骤。 下表中提供了部署中心设置。

    设置 Value
    组织 <your-GitHub-organization>
    存储库 nodejs-backend
    分支 主要

7. 使用服务主体进行 GitHub Actions 部署

部署中心配置已在每个示例存储库中创建了一个默认工作流文件,但它默认使用发布配置文件,而该配置文件使用基本身份验证。由于已禁用基本身份验证,如果你查看部署中心的“日志”选项卡,将看到自动触发的部署会导致错误。 必须修改工作流文件,以使用服务主体对应用服务进行身份验证。 如需示例工作流,请参阅将工作流文件添加到你的 GitHub 存储库

  1. 打开一个分支 GitHub 存储库并转到 <repo-name>/.github/workflows/ 目录。

  2. 选择自动生成的工作流文件,然后选择右上角的“铅笔”按钮编辑该文件。 将内容替换为以下文本(假设先前为凭据创建了 GitHub 机密)。 更新“env”节下的 <web-app-name> 占位符,然后直接提交到主分支。 此提交将使 GitHub Action 再次运行并部署代码,但这一次会使用服务主体进行身份验证。

    name: Build and deploy Node.js app to Azure Web App
    
    on:
      push:
        branches:
          - main
      workflow_dispatch:
    
    env:
      AZURE_WEBAPP_NAME: <web-app-name>   # set this to your application's name
      NODE_VERSION: '18.x'                # set this to the node version to use
      AZURE_WEBAPP_PACKAGE_PATH: '.'      # set this to the path to your web app project, defaults to the repository root
    
    jobs:
      build:
        runs-on: ubuntu-latest
    
        steps:
          - uses: actions/checkout@v2
    
          - name: Set up Node.js version
            uses: actions/setup-node@v1
            with:
              node-version: ${{ env.NODE_VERSION }}
    
          - name: npm install, build
            run: |
              npm install
              npm run build --if-present
    
          - name: Upload artifact for deployment job
            uses: actions/upload-artifact@v2
            with:
              name: node-app
              path: .
    
      deploy:
        runs-on: ubuntu-latest
        needs: build
        environment:
          url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
    
        steps:
          - name: Download artifact from build job
            uses: actions/download-artifact@v2
            with:
              name: node-app
          - uses: azure/login@v1
            with:
              creds: |
                {
                  "clientId": "${{ secrets.AZURE_APP_ID }}",
                  "clientSecret":  "${{ secrets.AZURE_PASSWORD }}",
                  "subscriptionId": "${{ secrets.AZURE_SUBSCRIPTION_ID }}",
                  "tenantId": "${{ secrets.AZURE_TENANT_ID }}"
                }
    
          - name: 'Deploy to Azure Web App'
            id: deploy-to-webapp
            uses: azure/webapps-deploy@v2
            with:
              app-name: ${{ env.AZURE_WEBAPP_NAME }}
              package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
    
          - name: logout
            run: |
              az logout
    
  3. 对其他分支 GitHub 存储库中的工作流文件重复此过程。

新的 GitHub 提交将为每个应用触发另一个部署。 这一次,部署应会成功,因为工作流使用服务主体对应用的 SCM 站点进行身份验证。

有关如何使用 GitHub Actions 等提供程序配置持续部署的详细指导,请参阅持续部署到 Azure 应用服务

8. 验证连接和应用访问

  1. 使用前端 Web 应用的 URL 浏览到该应用:https://<frontend-app-name>.azurewebsites.net

  2. 在文本框中,输入后端 Web 应用的 URL:https://<backend-app-name>.azurewebsites.net。 如果正确设置了连接,则应会看到消息“Hello from the backend Web app!”,这就是后端 Web 应用的整个内容。 来自前端 Web 应用的所有出站流量都通过虚拟网络进行路由。 前端 Web 应用通过专用终结点安全连接到后端 Web 应用。 如果连接出现问题,前端 Web 应用将会崩溃。

  3. 尝试使用后端 Web 应用的 URL 直接导航到该应用:https://<backend-app-name>.azurewebsites.net。 应该看到消息 Web App - Unavailable。 如果可以访问该应用,请确保已配置专用终结点,并将应用的访问限制设置为拒绝主 Web 应用的所有流量。

  4. 若要进一步验证前端 Web 应用是否通过专用链接访问后端 Web 应用,请通过 SSH 连接到某个前端实例。 若要通过 SSH 进行连接,请运行以下命令,以便与应用的 Web 容器建立 SSH 会话,并在浏览器中打开远程 shell。

    az webapp ssh --resource-group $groupName --name <frontend-app-name>
    
  5. 当 shell 在浏览器中打开后,运行 nslookup 以确认使用后端 Web 应用的专用 IP 访问后端 Web 应用。 还可以运行 curl 来再次验证站点内容。 将 <backend-app-name> 替换为后端 Web 应用名称。

    nslookup <backend-app-name>.azurewebsites.net
    curl https://<backend-app-name>.azurewebsites.net
    

    Screenshot of SSH session showing how to validate app connections.

    nslookup 应解析为后端 Web 应用的专用 IP 地址。 专用 IP 地址应是虚拟网络中的一个地址。 若要确认专用 IP,请转到后端 Web 应用的“网络”页。

    Screenshot of App Service Networking page showing the inbound IP of the app.

  6. 从另一个终端(不应是前端实例上的 SSH 会话)重复上述 nslookupcurl 命令。

    Screenshot of an external terminal doing a nslookup and curl of the back-end web app.

    nslookup 返回后端 Web 应用的公共 IP。 由于已禁用对后端 Web 应用的公共访问,因此,如果你尝试访问公共 IP,则会收到拒绝访问错误。 此错误表示无法从公共 Internet 访问此站点,这是预期行为。 nslookup 不会解析为专用 IP,因为它只能通过专用 DNS 区域从虚拟网络内部进行解析。 只有前端 Web 应用位于虚拟网络中。 如果你尝试从外部终端在后端 Web 应用上运行 curl,则返回的 HTML 包含 Web App - Unavailable。 此错误会显示你先前尝试在浏览器中导航到后端 Web 应用时看到的错误页的 HTML。

9.清理资源

在前面的步骤中,你在资源组中创建了 Azure 资源。 如果认为将来不需要这些资源,请在 Cloud Shell 中运行以下命令,以便删除资源组。

az group delete --name myresourcegroup

此命令可能需要花费几分钟时间运行。

常见问题

除了使用服务主体进行部署外,是否还有替代的部署方法?

由于在本教程中你已禁用基本身份验证,因此无法使用用户名和密码对后端 SCM 站点进行身份验证,也不能使用发布配置文件进行身份验证。 除了使用服务主体外,还可以使用 OpenID Connect

在应用服务中配置 GitHub Actions 部署时会发生什么情况?

Azure 会在存储库中自动生成工作流文件。 选定存储库和分支中的新提交现在将持续部署到应用服务应用中。 可以在“日志”选项卡中,跟踪提交和部署。

会将一个使用发布配置文件向应用服务进行身份验证的默认工作流文件添加到 GitHub 存储库。 可以转到 <repo-name>/.github/workflows/ 目录来查看此文件。

让后端 SCM 保持可公开访问是否安全?

锁定 FTP 和 SCM 访问时,确保只有 Microsoft Entra 提供支持的主体可访问 SCM 终结点,即使该终结点可公开访问也是这样。 此设置应确保后端 Web 应用仍然安全。

是否可以在完全不打开后端 SCM 站点的情况下进行部署?

如果你担心启用对 SCM 站点的公共访问会带来安全风险,或者你受到策略的限制,请考虑其他应用服务部署选项,例如从 ZIP 包运行

如何使用 ARM/Bicep 部署此体系结构?

可以使用 ARM/Bicep 模板部署你在本教程中创建的资源。 使用“连接到后端 Web 应用的应用”Bicep 模板可以创建安全的 N 层应用解决方案。

若要了解如何部署 ARM/Bicep 模板,请参阅如何使用 Bicep 和 Azure CLI 部署资源

后续步骤