使用 SAS 權杖保護 Azure 地圖服務帳戶

本文說明如何使用安全儲存的 SAS 權杖來建立 Azure 地圖服務帳戶,以用來呼叫 Azure 地圖服務 REST API。

必要條件

  • Azure 訂用帳戶。 如果您還沒有 Azure 帳戶,請註冊即可免費使用

  • Azure 訂用帳戶的擁有者角色權限。 您需要擁有者權限才能:

    • Azure Key Vault 中建立金鑰保存庫。
    • 建立使用者指派的受控識別。
    • 為受控識別指派角色。
    • 建立 Azure 地圖服務帳戶。
  • 已安裝 Azure CLI 以部署資源。

範例案例:SAS 權杖安全儲存體

SAS 權杖認證將其指定的存取層級授與持有認證的任何人,直到權杖到期或存取撤銷為止。 使用 SAS 權杖驗證的應用程式應安全地儲存金鑰。

此案例將 SAS 權杖安全地存儲存為金鑰保存庫中的祕密,並將權杖散發到公共用戶端。 應用程式生命週期事件可以產生新的 SAS 權杖,而不會中斷使用現有權杖的作用中連線。

如需設定金鑰保存庫的詳細資訊,請參閱 Azure Key Vault 開發人員指南

下列範例案例使用兩個 Azure Resource Manager (ARM) 範本部署以執行下列步驟:

  1. 建立金鑰保存庫。
  2. 建立使用者指派的受控識別。
  3. 將 Azure 角色型存取控制 (RBAC) Azure 地圖服務資料讀取者角色指派給使用者指派的受控識別。
  4. 建立具有跨原始資源共用 (CORS) 組態的 Azure 地圖服務帳戶,並附加使用者指派的受控識別。
  5. 在 Azure 金鑰保存庫中建立並儲存 SAS 權杖。
  6. 從金鑰保存庫擷取 SAS 權杖祕密。
  7. 建立使用 SAS 權杖的 Azure 地圖服務 REST API 要求。

當您完成時,您應該會看到使用 Azure CLI 在 PowerShell 上 Azure 地圖服務 Search Address (Non-Batch) REST API 結果。 Azure 資源會部署具有連線到 Azure 地圖服務帳戶的權限。 存在對最大速率限制、允許的區域、localhost 設定的 CORS 原則和 Azure RBAC 的控制。

使用 Azure CLI 進行 Azure 資源部署

下列步驟說明如何使用 SAS 權杖驗證來建立和設定 Azure 地圖服務帳戶。 在此範例中,Azure CLI 會在 PowerShell 執行個體中執行。

  1. 使用 az login 登入您的 Azure 訂用帳戶。

  2. 為您的訂用帳戶註冊金鑰保存庫、受控識別和 Azure 地圖服務。

    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 地圖服務帳戶相同的 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 地圖服務帳戶、角色指派和 SAS 權杖。

    注意

    Azure 地圖服務 Gen1 定價層淘汰

    Gen1 定價層現已遭取代,且將於 2026/9/15 淘汰。 Gen2 定價層會取代 Gen1 (S0 和 S1) 定價層。 如果 Azure 地圖服務帳戶已選取 Gen1 定價層,您可以在淘汰之前切換至 Gen2 定價,否則將會自動更新。 如需詳細資訊,請參閱管理 Azure 地圖服務帳戶的定價層

    {
        "$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. 使用您在上一步中建立的金鑰保存庫和受控識別資源中的識別碼參數部署範本。 為 <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 地圖服務端點提出要求來測試 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 地圖服務帳戶、設定角色指派和受控識別,並將 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 地圖服務 API 的要求。 Postman 將 API 要求轉換為您選擇的幾乎任何程式設計語言或架構的基本用戶端程式碼片段。 您可以在前端應用程式中使用此產生的程式碼片段。

下列小型 JavaScript 程式碼範例展示如何將 SAS 權杖與 JavaScript Fetch API 搭配使用來取得和傳回 Azure 地圖服務資訊。 此範例使用取得搜尋地址 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 地圖服務帳戶:

如需更詳細的範例,請參閱:

尋找 Azure 地圖服務帳戶的 API 使用計量:

探索範例,了解如何使用 Azure 地圖服務與 Microsoft Entra ID 整合: