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

使用 SAS 令牌保护 Azure Maps 帐户

本文介绍如何使用可用于调用 Azure Maps REST API 的安全存储 SAS 令牌创建 Azure Maps 帐户。

先决条件

  • Azure 订阅。 如果还没有 Azure 帐户,请注册免费帐户

  • Azure 订阅的所有者角色权限。 需要“所有者”权限才能执行以下操作:

    • Azure 密钥保管库中创建密钥保管库。
    • 创建用户分配的托管标识。
    • 为托管标识分配一个角色。
    • 创建 Azure Maps 帐户。
  • 安装 Azure CLI 以部署资源。

方案示例:SAS 令牌安全存储

SAS 令牌凭据将其指定的访问级别授予持有它的任何人,直到令牌过期或访问权限被撤销。 使用 SAS 令牌身份验证的应用程序应将密钥存储在安全存储中。

此方案将 SAS 令牌作为机密安全地存储在密钥保管库中,并将令牌分发到公共客户端。 应用程序生命周期事件可以生成新的 SAS 令牌,而不会中断使用现有令牌的活动连接。

有关配置密钥保管库的详细信息,请参阅 Azure 密钥保管库开发人员指南

以下示例方案使用两个 Azure 资源管理器 (ARM) 模板部署来执行以下步骤:

  1. 创建密钥保管库。
  2. 创建用户分配的托管标识。
  3. 将 Azure 基于角色的访问控制 (RBAC) Azure Maps 数据读取者角色分配给用户分配的托管标识。
  4. 使用跨源资源共享创建 Azure Maps 帐户 (CORS) 配置,并附加用户分配的托管标识。
  5. 创建 SAS 令牌并将其保存到 Azure 密钥保管库。
  6. 从密钥保管库服务器检索 SAS 令牌机密。
  7. 使用 SAS 令牌创建 Azure Maps REST API 请求。

完成后,应该会在带有 Azure CLI 的 PowerShell 上看到 Azure Maps Search Address (Non-Batch) REST API 结果。 Azure 资源部署时具有连接到 Azure Maps 帐户的权限。 针对最大速率限制、允许的区域、localhost 配置的 CORS 策略和 Azure RBAC 进行了控制。

使用 Azure CLI 进行 Azure 资源部署

以下步骤介绍如何使用 SAS 令牌身份验证创建和配置 Azure Maps 帐户。 在此示例中,Azure CLI 在 PowerShell 实例中运行。

  1. 使用 az login 登录到 Azure 订阅。

  2. 为订阅注册密钥保管库、托管标识和 Azure Maps。

    az provider register --namespace Microsoft.KeyVault
    az provider register --namespace Microsoft.ManagedIdentity
    az provider register --namespace Microsoft.Maps
    
  3. 检索 Microsoft Entra 对象 ID。

    $id = $(az rest --method GET --url 'https://graph.microsoft.com/v1.0/me?$select=id' --headers 'Content-Type=application/json' --query "id")
    
  4. 创建一个名为 prereq.azuredeploy.json 的模板文件,其内容如下:

    {
        "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
        "contentVersion": "1.0.0.0",
        "parameters": {
            "location": {
                "type": "string",
                "defaultValue": "[resourceGroup().location]",
                "metadata": {
                    "description": "Specifies the location for all the resources."
                }
            },
            "keyVaultName": {
                "type": "string",
                "defaultValue": "[concat('vault', uniqueString(resourceGroup().id))]",
                "metadata": {
                    "description": "Specifies the name of the key vault."
                }
            },
            "userAssignedIdentityName": {
                "type": "string",
                "defaultValue": "[concat('identity', uniqueString(resourceGroup().id))]",
                "metadata": {
                    "description": "The name for your managed identity resource."
                }
            },
            "objectId": {
                "type": "string",
                "metadata": {
                    "description": "Specifies the object ID of a user, service principal, or security group in the Azure AD tenant for the vault. The object ID must be unique for the set of access policies. Get it by using Get-AzADUser or Get-AzADServicePrincipal cmdlets."
                }
            },
            "secretsPermissions": {
                "type": "array",
                "defaultValue": [
                    "list",
                    "get",
                    "set"
                ],
                "metadata": {
                    "description": "Specifies the permissions to secrets in the vault. Valid values are: all, get, list, set, delete, backup, restore, recover, and purge."
                }
            }
        },
        "resources": [
            {
                "type": "Microsoft.ManagedIdentity/userAssignedIdentities",
                "name": "[parameters('userAssignedIdentityName')]",
                "apiVersion": "2018-11-30",
                "location": "[parameters('location')]"
            },
            {
                "apiVersion": "2021-04-01-preview",
                "type": "Microsoft.KeyVault/vaults",
                "name": "[parameters('keyVaultName')]",
                "location": "[parameters('location')]",
                "properties": {
                    "tenantId": "[subscription().tenantId]",
                    "sku": {
                        "name": "Standard",
                        "family": "A"
                    },
                    "enabledForTemplateDeployment": true,
                    "accessPolicies": [
                        {
                            "objectId": "[parameters('objectId')]",
                            "tenantId": "[subscription().tenantId]",
                            "permissions": {
                                "secrets": "[parameters('secretsPermissions')]"
                            }
                        }
                    ]
                }
            }
        ],
        "outputs": {
            "userIdentityResourceId": {
                "type": "string",
                "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('userAssignedIdentityName'))]"
            },
            "userAssignedIdentityPrincipalId": {
                "type": "string",
                "value": "[reference(parameters('userAssignedIdentityName')).principalId]"
            },
            "keyVaultName": {
                "type": "string",
                "value": "[parameters('keyVaultName')]"
            }
        }
    }
    
    
  5. 部署你在上一步中创建的先决条件资源。 针对 <group-name> 提供自己的值。 请确保使用与 Azure Maps 帐户相同的 location 帐户。

    az group create --name <group-name> --location "East US"
    $outputs = $(az deployment group create --name ExampleDeployment --resource-group <group-name> --template-file "./prereq.azuredeploy.json" --parameters objectId=$id --query "[properties.outputs.keyVaultName.value, properties.outputs.userAssignedIdentityPrincipalId.value, properties.outputs.userIdentityResourceId.value]" --output tsv)
    
  6. 创建模板文件 azuredeploy.json 以预配 Azure Maps 帐户、角色分配和 SAS 令牌。

    注意

    Azure Maps Gen1 定价层停用

    Gen1 定价层现已弃用,并将于 2026 年 9 月 15 日开始停用。 Gen2 定价层将取代 Gen1(S0 和 S1)定价层。 如果你的 Azure Maps 帐户选择了 Gen1 定价层,你可以在停用之前切换到 Gen2 定价层,否则它会自动更新。 有关详细信息,请参阅管理 Azure Maps 帐户的定价层

    {
        "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
        "contentVersion": "1.0.0.0",
        "parameters": {
            "location": {
                "type": "string",
                "defaultValue": "[resourceGroup().location]",
                "metadata": {
                    "description": "Specifies the location for all the resources."
                }
            },
            "keyVaultName": {
                "type": "string",
                "metadata": {
                    "description": "Specifies the resourceId of the key vault."
                }
            },
            "accountName": {
                "type": "string",
                "defaultValue": "[concat('map', uniqueString(resourceGroup().id))]",
                "metadata": {
                    "description": "The name for your Azure Maps account."
                }
            },
            "userAssignedIdentityResourceId": {
                "type": "string",
                "metadata": {
                    "description": "Specifies the resourceId for the user assigned managed identity resource."
                }
            },
            "userAssignedIdentityPrincipalId": {
                "type": "string",
                "metadata": {
                    "description": "Specifies the resourceId for the user assigned managed identity resource."
                }
            },
            "pricingTier": {
                "type": "string",
                "allowedValues": [
                    "S0",
                    "S1",
                    "G2"
                ],
                "defaultValue": "G2",
                "metadata": {
                    "description": "The pricing tier for the account. Use S0 for small-scale development. Use S1 or G2 for large-scale applications."
                }
            },
            "kind": {
                "type": "string",
                "allowedValues": [
                    "Gen1",
                    "Gen2"
                ],
                "defaultValue": "Gen2",
                "metadata": {
                    "description": "The pricing tier for the account. Use Gen1 for small-scale development. Use Gen2 for large-scale applications."
                }
            },
            "guid": {
                "type": "string",
                "defaultValue": "[guid(resourceGroup().id)]",
                "metadata": {
                    "description": "Input string for new GUID associated with assigning built in role types."
                }
            },
            "startDateTime": {
                "type": "string",
                "defaultValue": "[utcNow('u')]",
                "metadata": {
                    "description": "Current Universal DateTime in ISO 8601 'u' format to use as the start of the SAS token."
                }
            },
            "duration" : {
                "type": "string",
                "defaultValue": "P1Y",
                "metadata": {
                    "description": "The duration of the SAS token. P1Y is maximum, ISO 8601 format is expected."
                }
            },
            "maxRatePerSecond": {
                "type": "int",
                "defaultValue": 500,
                "minValue": 1,
                "maxValue": 500,
                "metadata": {
                    "description": "The approximate maximum rate per second the SAS token can be used."
                }
            },
            "signingKey": {
                "type": "string",
                "defaultValue": "primaryKey",
                "allowedValues": [
                    "primaryKey",
                    "seconaryKey"
                ],
                "metadata": {
                    "description": "The specified signing key which will be used to create the SAS token."
                }
            },
            "allowedOrigins": {
                "type": "array",
                "defaultValue": [],
                "maxLength": 10,
                "metadata": {
                    "description": "The specified application's web host header origins (example: https://www.azure.com) which the Azure Maps account allows for CORS."
                }
            }, 
            "allowedRegions": {
                "type": "array",
                "defaultValue": [],
                "metadata": {
                    "description": "The specified SAS token allowed locations where the token may be used."
                }
            }
        },
        "variables": {
            "accountId": "[resourceId('Microsoft.Maps/accounts', parameters('accountName'))]",
            "Azure Maps Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '423170ca-a8f6-4b0f-8487-9e4eb8f49bfa')]",
            "sasParameters": {
                "signingKey": "[parameters('signingKey')]",
                "principalId": "[parameters('userAssignedIdentityPrincipalId')]",
                "maxRatePerSecond": "[parameters('maxRatePerSecond')]",
                "start": "[parameters('startDateTime')]",
                "expiry": "[dateTimeAdd(parameters('startDateTime'), parameters('duration'))]",
                "regions": "[parameters('allowedRegions')]"
            }
        },
        "resources": [
            {
                "name": "[parameters('accountName')]",
                "type": "Microsoft.Maps/accounts",
                "apiVersion": "2023-06-01",
                "location": "[parameters('location')]",
                "sku": {
                    "name": "[parameters('pricingTier')]"
                },
                "kind": "[parameters('kind')]",
                "properties": {
                    "cors": {
                        "corsRules": [
                            {
                                "allowedOrigins": "[parameters('allowedOrigins')]"
                            }
                        ]
                    }
                },
                "identity": {
                    "type": "UserAssigned",
                    "userAssignedIdentities": {
                        "[parameters('userAssignedIdentityResourceId')]": {}
                    }
                }
            },
            {
                "apiVersion": "2020-04-01-preview",
                "name": "[concat(parameters('accountName'), '/Microsoft.Authorization/', parameters('guid'))]",
                "type": "Microsoft.Maps/accounts/providers/roleAssignments",
                "dependsOn": [
                    "[parameters('accountName')]"
                ],
                "properties": {
                    "roleDefinitionId": "[variables('Azure Maps Data Reader')]",
                    "principalId": "[parameters('userAssignedIdentityPrincipalId')]",
                    "principalType": "ServicePrincipal"
                }
            },
            {
                "apiVersion": "2021-04-01-preview",
                "type": "Microsoft.KeyVault/vaults/secrets",
                "name": "[concat(parameters('keyVaultName'), '/', parameters('accountName'))]",
                "dependsOn": [
                    "[variables('accountId')]"
                ],
                "tags": {
                    "signingKey": "[variables('sasParameters').signingKey]",
                    "start" : "[variables('sasParameters').start]",
                    "expiry" : "[variables('sasParameters').expiry]"
                },
                "properties": {
                    "value": "[listSas(variables('accountId'), '2023-06-01', variables('sasParameters')).accountSasToken]"
                }
            }
        ]
    }
    
  7. 使用来自密钥保管库的 ID 参数和上一步中创建的托管标识资源部署模板。 针对 <group-name> 提供自己的值。 创建 SAS 令牌时,将 allowedRegions 参数设置为 eastuswestus2westcentralus。 然后,你可以使用这些位置向 us.atlas.microsoft.com 终结点发出 HTTP 请求。

    重要

    你将 SAS 令牌保存到密钥保管库中,以防止其凭据出现在 Azure 部署日志中。 SAS 令牌机密的 tags 还包含开始时间、过期时间和签名密钥名称,以表明 SAS 令牌何时过期。

     az deployment group create --name ExampleDeployment --resource-group <group-name> --template-file "./azuredeploy.json" --parameters keyVaultName="$($outputs[0])" userAssignedIdentityPrincipalId="$($outputs[1])" userAssignedIdentityResourceId="$($outputs[2])" allowedOrigins="['http://localhost']" allowedRegions="['eastus', 'westus2', 'westcentralus']" maxRatePerSecond="10"
    
  8. 从密钥保管库查找并保存单个 SAS 令牌机密的副本。

    $secretId = $(az keyvault secret list --vault-name $outputs[0] --query "[? contains(name,'map')].id" --output tsv)
    $sasToken = $(az keyvault secret show --id "$secretId" --query "value" --output tsv)
    
  9. 通过向 Azure Maps 终结点发出请求来测试 SAS 令牌。 此示例指定 us.atlas.microsoft.com 确保请求路由到美国地理位置。 你的 SAS 令牌允许美国地理范围内的区域。

    az rest --method GET --url 'https://us.atlas.microsoft.com/search/address/json?api-version=1.0&query=1 Microsoft Way, Redmond, WA 98052' --headers "Authorization=jwt-sas $($sasToken)" --query "results[].address"
    

完整脚本示例

若要运行完整示例,以下模板文件必须与当前 PowerShell 会话位于同一目录中:

  • prereq.azuredeploy.json,创建密钥保管库和托管标识。
  • azuredeploy.json,创建 Azure Maps 帐户,配置角色分配和托管标识,并将 SAS 令牌存储在密钥保管库中。
az login
az provider register --namespace Microsoft.KeyVault
az provider register --namespace Microsoft.ManagedIdentity
az provider register --namespace Microsoft.Maps

$id = $(az rest --method GET --url 'https://graph.microsoft.com/v1.0/me?$select=id' --headers 'Content-Type=application/json' --query "id")
az group create --name <group-name> --location "East US"
$outputs = $(az deployment group create --name ExampleDeployment --resource-group <group-name> --template-file "./prereq.azuredeploy.json" --parameters objectId=$id --query "[properties.outputs.keyVaultName.value, properties.outputs.userAssignedIdentityPrincipalId.value, properties.outputs.userIdentityResourceId.value]" --output tsv)
az deployment group create --name ExampleDeployment --resource-group <group-name> --template-file "./azuredeploy.json" --parameters keyVaultName="$($outputs[0])" userAssignedIdentityPrincipalId="$($outputs[1])" userAssignedIdentityResourceId="$($outputs[2])" allowedOrigins="['http://localhost']" allowedRegions="['eastus', 'westus2', 'westcentralus']" maxRatePerSecond="10"
$secretId = $(az keyvault secret list --vault-name $outputs[0] --query "[? contains(name,'map')].id" --output tsv)
$sasToken = $(az keyvault secret show --id "$secretId" --query "value" --output tsv)

az rest --method GET --url 'https://us.atlas.microsoft.com/search/address/json?api-version=1.0&query=1 Microsoft Way, Redmond, WA 98052' --headers "Authorization=jwt-sas $($sasToken)" --query "results[].address"

实际示例

可以从大多数客户端(如 C#、Java 或 JavaScript)运行对 Azure Maps API 的请求。 API 开发平台(如 brunoPostman)可以将 API 请求转换为你选择的几乎任何编程语言或框架的基本客户端代码片段。 可以在前端应用程序中使用生成的这个代码片段。

以下小型 JavaScript 代码示例展示了如何将 SAS 令牌与 JavaScript Fetch API 一起使用来获取和返回 Azure Maps 信息。 该示例使用了 Get Search Address API 1.0 版。 针对 <your SAS token> 提供自己的值。

要使此示例正常工作,请确保从与 API 调用的 allowedOrigins 相同的源中运行此示例。 例如,如果你在 API 调用中提供 https://contoso.com 作为 allowedOrigins,则托管 JavaScript 脚本的 HTML 页面应该是 https://contoso.com

async function getData(url = 'https://us.atlas.microsoft.com/search/address/json?api-version=1.0&query=1 Microsoft Way, Redmond, WA 98052') {
  const response = await fetch(url, {
    method: 'GET',
    mode: 'cors',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'jwt-sas <your SAS token>',
    }
  });
  return response.json(); // parses JSON response into native JavaScript objects
}

postData('https://us.atlas.microsoft.com/search/address/json?api-version=1.0&query=1 Microsoft Way, Redmond, WA 98052')
  .then(data => {
    console.log(data); // JSON data parsed by `data.json()` call
  });

清理资源

不再需要 Azure 资源时,可将其删除:

az group delete --name {group-name}

后续步骤

部署快速入门 ARM 模板以创建使用 SAS 令牌的 Azure Maps 帐户:

有关更多详细示例,请参阅:

查找 Azure Maps 帐户的 API 使用指标:

探索演示如何将 Microsoft Entra ID 与 Azure Maps 集成的示例: