共用方式為


了解如何在 Azure VM 上使用 Azure 資源受控識別來取得存取權杖

Azure 資源受控識別是 Microsoft Entra ID 的一項功能。 每個支援適用於 Azure 資源的受控識別 Azure 服務均受限於其本身的時間表。 在開始之前,請務必先檢閱資源的受控識別可用性狀態和已知問題

資源的受控身分識別會在 Microsoft Entra ID 中為 Azure 服務提供自動受控身分識別。 您可以使用此身分識別來向任何支援 Microsoft Entra 驗證的服務進行驗證,不需要任何您程式碼中的認證。

此文章提供用於取得權杖的各種程式碼和指令碼範例。 它也包含處理權杖到期和 HTTP 錯誤的相關指引。

必要條件

  • 如果您不熟悉適用於 Azure 資源的受控識別功能,請參閱此概觀。 如果您沒有 Azure 帳戶,請先註冊免費帳戶,再繼續進行。

如果您打算使用本文中的 Azure PowerShell 範例,請務必安裝最新版的 Azure PowerShell

重要

  • 本文中的所有範例程式碼/指令碼都假設用戶端是在具有 Azure 資源受控識別的虛擬機器上執行。 在 Azure 入口網站中使用虛擬機器「連線」功能,從遠端連線到您的虛擬機器。 如需有關在虛擬機器上啟用 Azure 資源受控識別的詳細資訊,請參閱使用 Azure 入口網站在虛擬機器上設定 Azure 資源受控識別,或其中一篇變化文章 (使用 PowerShell、CLI、範本或 Azure SDK)。

重要

  • Azure 資源受控識別的安全性界限,是將在其中使用身分識別的資源。 所有在虛擬機器上執行的程式碼/指令碼,都可以針對其上提供的任何受控識別要求及擷取權杖。

概觀

用戶端應用程式可以要求受控服務識別僅限應用程式的存取權杖,以存取指定資源。 此權杖是以 Azure 資源受控識別服務原則為基礎。 因此,用戶端不需要取得本身服務主體下的存取權杖。 權杖在需要用戶端認證的服務對服務呼叫中適合作為持有人權杖。

連結 描述
使用 HTTP 取得權杖 Azure 資源受控識別權杖端點的通訊協定詳細資料
使用 Azure.Identity 取得權杖 使用 Azure.Identity 程式庫取得權杖
使用適用於 .NET 的 Microsoft.Azure.Services.AppAuthentication 程式庫取得權杖 從 .NET 用戶端使用 Microsoft.Azure.Services.AppAuthentication 程式庫的範例
使用 C# 取得權杖 從 C# 用戶端使用 Azure 資源受控識別 REST 端點的範例
使用 Java 取得權杖 從 Java 用戶端使用 Azure 資源受控識別 REST 端點的範例
使用 Go 取得權杖 從 Go 用戶端使用 Azure 資源受控識別 REST 端點的範例
使用 PowerShell 取得權杖 從 PowerShell 用戶端使用 Azure 資源受控識別 REST 端點的範例
使用 CURL 取得權杖 從 Bash/CURL 用戶端使用 Azure 資源受控識別 REST 端點的範例
處理權杖快取 處理過期存取權杖的指引
錯誤處理 此指引可處理從 Azure 資源受控識別權杖端點傳回的 HTTP 錯誤
Azure 服務的資源識別碼 取得所支援 Azure 服務資源識別碼的地方

使用 HTTP 取得權杖

取得存取權杖的基本介面是以 REST 為基礎,如此可讓用戶端應用程式在可執行 HTTP REST 呼叫的虛擬機器上執行時,可以對其進行存取。 此方法類似於 Microsoft Entra 的程式設計模型,但用戶端會使用虛擬機器上的端點 (對比於 Microsoft Entra 端點)。

使用 Azure Instance Metadata Service (IMDS) 端點 (建議) 的範例要求:

GET 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/' HTTP/1.1 Metadata: true
元素 描述
GET HTTP 指令動詞,指出您想要擷取端點中的資料。 在此案例中是 OAuth 存取權杖。
http://169.254.169.254/metadata/identity/oauth2/token 適用於 Instance Metadata Service 的 Azure 資源受控識別端點。
api-version 一個查詢字串參數,指出 IMDS 端點的 API 版本。 使用 API 2018-02-01 版或更新的版本。
resource 查詢字串參數,指出目標資源的應用程式識別碼 URI。 也會出現在所核發權杖的 aud (對象) 宣告中。 此範例會要求用來存取 Azure Resource Manager 的權杖,其中包含應用程式識別碼 URI https://management.azure.com/
Metadata 受控識別所需的 HTTP 要求標頭欄位。 此資訊可用來作為伺服器端偽造要求 (SSRF) 攻擊的緩解措施。 此值必須設定為 "true" (全部小寫)。
object_id (選擇性) 查詢字串參數,指出要使用權杖的受控識別 object_id。 如果您的 VM 有多個使用者指派的受控識別,這會是必要項目。
client_id (選擇性) 查詢字串參數,指出要使用權杖的受控識別 client_id。 如果您的 VM 有多個使用者指派的受控識別,這會是必要項目。
msi_res_id (選用) 查詢字串參數,指出要取得其權杖之受控識別的 msi_res_id (Azure 資源識別碼)。 如果您的 VM 有多個使用者指派的受控識別,這會是必要項目。

範例回應:

HTTP/1.1 200 OK
Content-Type: application/json
{
  "access_token": "eyJ0eXAi...",
  "refresh_token": "",
  "expires_in": "3599",
  "expires_on": "1506484173",
  "not_before": "1506480273",
  "resource": "https://management.azure.com/",
  "token_type": "Bearer"
}
元素 描述
access_token 要求的存取權杖。 呼叫受保護的 REST API 時,權杖會內嵌在 Authorization 要求標頭欄位中成為「持有人」權杖,以允許 API 驗證呼叫端。
refresh_token 並未由 Azure 資源受控識別使用。
expires_in 存取權杖從發行到過期之前持續有效的秒數。 在權杖的 iat 宣告中可找到發行時間。
expires_on 存取權杖到期的時間範圍。 日期以 "1970-01-01T0:0:0Z UTC" 起算的秒數表示 (對應至權杖的 exp 宣告)。
not_before 存取權杖生效且可被接受的時間範圍。 日期以 "1970-01-01T0:0:0Z UTC" 起算的秒數表示 (對應至權杖的 nbf 宣告)。
resource 要求存取權杖所針對的資源,符合要求的 resource 查詢字串參數。
token_type 權杖的類型,即「持有人」存取權杖,表示此權杖的持有人可以存取資源。

使用 Azure 身分識別用戶端程式庫取得權杖

使用 Azure 身分識別用戶端程式庫是使用受控識別的建議方式。 所有 Azure SDK 都會與提供 DefaultAzureCredential 支援的 Azure.Identity 程式庫整合。 此類別可讓您輕鬆使用受控識別搭配 Azure SDK。深入了解

  1. 安裝 Azure.Identity 套件和其他必要的 Azure SDK 程式庫套件,例如 Azure.Security.KeyVault.Secrets

  2. 使用下列範例程式碼。 您不需要擔心取得權杖。 您可以直接使用 Azure SDK 用戶端。 該程式碼可用來示範如何取得權杖 (如果您需要)。

    using Azure.Core;
    using Azure.Identity;
    
    string userAssignedClientId = "<your managed identity client Id>";
    var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = userAssignedClientId });
    var accessToken = credential.GetToken(new TokenRequestContext(new[] { "https://vault.azure.net" }));
    // To print the token, you can convert it to string 
    String accessTokenString = accessToken.Token.ToString();
    
    //You can use the credential object directly with Key Vault client.     
    var client = new SecretClient(new Uri("https://myvault.vault.azure.net/"), credential);
    

使用適用於 .NET 的 Microsoft.Azure.Services.AppAuthentication 程式庫取得權杖

對於 .NET 應用程式和函式,使用 Azure 資源受控識別的最簡單方式就是透過 Microsoft.Azure.Services.AppAuthentication 套件。 此程式庫也將讓您在您的開發機器的本機上測試程式碼。 您可以使用來自 Visual Studio、Azure CLI 或 Active Directory 整合式驗證的使用者帳戶來測試程式碼。 如需使用此程式庫之本機開發選項的詳細資訊,請參閱 Microsoft.Azure.Services.AppAuthentication 參考。 本節示範如何在您的程式碼中開始使用程式庫。

  1. Microsoft.Azure.Services.AppAuthenticationMicrosoft.Azure.KeyVault NuGet 套件的參考新增至應用程式。

  2. 將下列程式碼新增到您的應用程式:

    using Microsoft.Azure.Services.AppAuthentication;
    using Microsoft.Azure.KeyVault;
    // ...
    var azureServiceTokenProvider = new AzureServiceTokenProvider();
    string accessToken = await azureServiceTokenProvider.GetAccessTokenAsync("https://management.azure.com/");
    // OR
    var kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
    

若要深入了解 Microsoft.Azure.Services.AppAuthentication 和它公開的作業,請參閱 Microsoft.Azure.Services.AppAuthentication 參考採用 Azure 資源受控識別的 App Service 和 KeyVault .NET 範例

使用 C# 取得權杖

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Web.Script.Serialization; 

// Build request to acquire managed identities for Azure resources token
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/");
request.Headers["Metadata"] = "true";
request.Method = "GET";

try
{
    // Call /token endpoint
    HttpWebResponse response = (HttpWebResponse)request.GetResponse();

    // Pipe response Stream to a StreamReader, and extract access token
    StreamReader streamResponse = new StreamReader(response.GetResponseStream()); 
    string stringResponse = streamResponse.ReadToEnd();
    JavaScriptSerializer j = new JavaScriptSerializer();
    Dictionary<string, string> list = (Dictionary<string, string>) j.Deserialize(stringResponse, typeof(Dictionary<string, string>));
    string accessToken = list["access_token"];
}
catch (Exception e)
{
    string errorText = String.Format("{0} \n\n{1}", e.Message, e.InnerException != null ? e.InnerException.Message : "Acquire token failed");
}

使用 Java 取得權杖

使用此 JSON 文件庫擷取使用 Java 的權杖。

import java.io.*;
import java.net.*;
import com.fasterxml.jackson.core.*;
 
class GetMSIToken {
    public static void main(String[] args) throws Exception {
 
        URL msiEndpoint = new URL("http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/");
        HttpURLConnection con = (HttpURLConnection) msiEndpoint.openConnection();
        con.setRequestMethod("GET");
        con.setRequestProperty("Metadata", "true");
 
        if (con.getResponseCode()!=200) {
            throw new Exception("Error calling managed identity token endpoint.");
        }
 
        InputStream responseStream = con.getInputStream();
 
        JsonFactory factory = new JsonFactory();
        JsonParser parser = factory.createParser(responseStream);
 
        while(!parser.isClosed()){
            JsonToken jsonToken = parser.nextToken();
 
            if(JsonToken.FIELD_NAME.equals(jsonToken)){
                String fieldName = parser.getCurrentName();
                jsonToken = parser.nextToken();
 
                if("access_token".equals(fieldName)){
                    String accesstoken = parser.getValueAsString();
                    System.out.println("Access Token: " + accesstoken.substring(0,5)+ "..." + accesstoken.substring(accesstoken.length()-5));
                    return;
                }
            }
        }
    }
}

使用 Go 取得權杖

package main

import (
  "fmt"
  "io/ioutil"
  "net/http"
  "net/url"
  "encoding/json"
)

type responseJson struct {
  AccessToken string `json:"access_token"`
  RefreshToken string `json:"refresh_token"`
  ExpiresIn string `json:"expires_in"`
  ExpiresOn string `json:"expires_on"`
  NotBefore string `json:"not_before"`
  Resource string `json:"resource"`
  TokenType string `json:"token_type"`
}

func main() {
    
    // Create HTTP request for a managed services for Azure resources token to access Azure Resource Manager
    var msi_endpoint *url.URL
    msi_endpoint, err := url.Parse("http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01")
    if err != nil {
      fmt.Println("Error creating URL: ", err)
      return 
    }
    msi_parameters := msi_endpoint.Query()
    msi_parameters.Add("resource", "https://management.azure.com/")
    msi_endpoint.RawQuery = msi_parameters.Encode()
    req, err := http.NewRequest("GET", msi_endpoint.String(), nil)
    if err != nil {
      fmt.Println("Error creating HTTP request: ", err)
      return 
    }
    req.Header.Add("Metadata", "true")

    // Call managed services for Azure resources token endpoint
    client := &http.Client{}
    resp, err := client.Do(req) 
    if err != nil{
      fmt.Println("Error calling token endpoint: ", err)
      return
    }

    // Pull out response body
    responseBytes,err := ioutil.ReadAll(resp.Body)
    defer resp.Body.Close()
    if err != nil {
      fmt.Println("Error reading response body : ", err)
      return
    }

    // Unmarshall response body into struct
    var r responseJson
    err = json.Unmarshal(responseBytes, &r)
    if err != nil {
      fmt.Println("Error unmarshalling the response:", err)
      return
    }

    // Print HTTP response and marshalled response body elements to console
    fmt.Println("Response status:", resp.Status)
    fmt.Println("access_token: ", r.AccessToken)
    fmt.Println("refresh_token: ", r.RefreshToken)
    fmt.Println("expires_in: ", r.ExpiresIn)
    fmt.Println("expires_on: ", r.ExpiresOn)
    fmt.Println("not_before: ", r.NotBefore)
    fmt.Println("resource: ", r.Resource)
    fmt.Println("token_type: ", r.TokenType)
}

使用 PowerShell 取得權杖

下列範例示範如何從 PowerShell 用戶端使用 Azure 資源受控識別 REST 端點來執行下列動作:

  1. 取得存取權杖。
  2. 使用存取權杖來呼叫 Azure Resource Manager REST API,並取得虛擬機器的相關資訊。 請務必以您的訂用帳戶識別碼、資源群組名稱和虛擬機器名稱各別取代 <SUBSCRIPTION-ID><RESOURCE-GROUP><VM-NAME>
Invoke-WebRequest -Uri 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F' -Headers @{Metadata="true"}

如何對來自回應的存取權杖進行剖析的範例:

# Get an access token for managed identities for Azure resources
$response = Invoke-WebRequest -Uri 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F' `
                              -Headers @{Metadata="true"}
$content =$response.Content | ConvertFrom-Json
$access_token = $content.access_token
echo "The managed identities for Azure resources access token is $access_token"

# Use the access token to get resource information for the VM
$vmInfoRest = (Invoke-WebRequest -Uri 'https://management.azure.com/subscriptions/<SUBSCRIPTION-ID>/resourceGroups/<RESOURCE-GROUP>/providers/Microsoft.Compute/virtualMachines/<VM-NAME>?api-version=2017-12-01' -Method GET -ContentType "application/json" -Headers @{ Authorization ="Bearer $access_token"}).content
echo "JSON returned from call to get VM info:"
echo $vmInfoRest

使用 CURL 取得權杖

curl 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F' -H Metadata:true -s

如何對來自回應的存取權杖進行剖析的範例:

response=$(curl 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F' -H Metadata:true -s)
access_token=$(echo $response | python -c 'import sys, json; print (json.load(sys.stdin)["access_token"])')
echo The managed identities for Azure resources access token is $access_token

權杖快取

受控識別子系統會快取權杖,但仍建議您在程式碼中實作權杖快取。 您應該為資源指出權杖已到期的情況做好準備。

只有在以下情況中,才會向 Microsoft Entra ID 進行線上呼叫:

  • 因為 Azure 資源子系統快取的受控識別中沒有權杖,而發生遺漏快取。
  • 快取的權杖到期。

錯誤處理

受控識別端點會透過 HTTP 回應訊息標頭的狀態碼欄位 (如 4xx 或 5xx 錯誤) 來發出錯誤通知:

狀態碼 錯誤原因 處理方式
404 找不到。 正在更新 IMDS 端點。 使用指數輪詢重試。 請參閱下面的指引。
410 IMDS 正在進行更新 IMDS 將可在 70 秒內使用
429 要求太多。 已達到 IMDS 節流限制。 使用指數輪詢重試。 請參閱下面的指引。
要求中的 4xx 錯誤。 一個或多個要求參數不正確。 請勿重試。 檢查錯誤詳細資料以取得更多資訊。 4xx 錯誤是設計階段錯誤。
來自服務的 5xx 暫時性錯誤。 Azure 資源子系統受控識別或 Microsoft Entra ID 傳回了暫時性錯誤。 等待至少 1 秒後即可安全地進行重試。 如果您太快重試或重試太多次,IMDS 和/或 Microsoft Entra ID 可能會傳回速率限制錯誤 (429)。
timeout 正在更新 IMDS 端點。 使用指數輪詢重試。 請參閱下面的指引。

如果發生錯誤,對應的 HTTP 回應主體會包含 JSON 格式的錯誤詳細資料:

元素 描述
error 錯誤識別碼。
error_description 錯誤的詳細資訊描述。 錯誤描述可以隨時變更。 請勿將程式碼撰寫為會針對錯誤描述中的值建立分支。

HTTP 回應參考

本節會說明可能的錯誤回應。 「200 確定」狀態是成功的回應,而且存取權杖會包含在回應主體 JSON 中 (在 access_token 元素中)。

狀態碼 錯誤 錯誤描述 解決方案
400 不正確的要求 invalid_resource AADSTS50001:在名為 <TENANT-ID> 的租用戶中找不到名為 <URI> 的應用程式。 此訊息會顯示租用戶系統管理員是否尚未安裝應用程式,或沒有任何租用戶使用者同意。 您可能會將驗證要求傳送至錯誤的租用戶。\ (僅限 Linux)
400 不正確的要求 bad_request_102 未指定必要的中繼資料標頭 要求中遺漏 Metadata 要求標頭欄位,或欄位的格式不正確。 值必須指定為 true (全部小寫)。 相關範例請參閱前一節 REST 中的「範例要求」。
401 未經授權 unknown_source 未知的來源 <URI> 請確認 HTTP GET 要求 URI 的格式正確。 scheme:host/resource-path 部分必須指定為 http://localhost:50342/oauth2/token。 相關範例請參閱前一節 REST 中的「範例要求」。
invalid_request 要求遺漏必要參數、包含無效參數值、多次包含某個參數或格式不正確。
unauthorized_client 用戶端未獲授權,無法使用此方法要求存取權杖。 由 VM 上的要求所造成,該 VM 未正確地設定 Azure 資源受控識別。 如果您需要設定虛擬機器的協助,請參閱使用 Azure 入口網站在虛擬機器上設定 Azure 資源受控識別
access_denied 資源擁有者或授權伺服器已拒絕要求。
unsupported_response_type 授權伺服器不支援使用此方法取得存取權杖。
invalid_scope 要求的範圍無效、未知或格式不正確。
500 內部伺服器錯誤 未知 無法從 Active 目錄擷取權杖。 如需詳細資訊,請參閱<檔案路徑>中的記錄 驗證 VM 已針對 Azure 資源啟用受控識別。 如果您需要設定虛擬機器的協助,請參閱使用 Azure 入口網站在虛擬機器上設定 Azure 資源受控識別

也請確認 HTTP GET 要求 URI 的格式正確,尤其是查詢字串中指定的資源 URI。 相關範例請參閱前一節 REST 中的「範例要求」,或請參閱支援 Microsoft Entra 驗證的 Azure 服務,以取得服務及其各自資源識別碼的清單。

重要

重試指引

若您收到 404、429 或 5xx 錯誤碼,建議您重試 (請參閱上方的錯誤處理)。 如果您收到 410 錯誤,表示 IMDS 正在進行更新,最多 70 秒即可使用。

節流限制會套用至對 IMDS 端點進行的呼叫數目。 超過節流閾值時,IMDS 端點會在節流生效時,限制任何進一步的要求。 在這段期間,IMDS 端點會傳回 HTTP 狀態碼 429 (「太多要求」),且要求會失敗。

對於重試,我們建議下列策略:

重試策略 設定 運作方式
ExponentialBackoff 重試計數
最小輪詢
最大輪詢
差異輪詢
第一個快速重試
5
0 秒
60 秒
2 秒
false
嘗試 1 - 延遲 0 秒
嘗試 2 - 延遲 ~2 秒
嘗試 3 - 延遲 ~6 秒
嘗試 4 - 延遲 ~14 秒
嘗試 5 - 延遲 ~30 秒

Azure 服務的資源識別碼

如需支援 Azure 資源受控識別的資源清單,請參閱具有受控識別支援的 Azure 服務

下一步