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

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

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

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

必要條件

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

重要

  • 本文中的所有範例程式碼/腳本假設用戶端是在具有 Azure 資源的受控識別的虛擬機器上執行。 使用Azure 入口網站中的虛擬機器「連線」功能,從遠端連線到您的 VM。 如需在 VM 上為 Azure 資源啟用受控識別的詳細資訊,請參閱 使用Azure 入口網站 設定 VM 上的 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 為基礎,讓任何可在 VM 上執行的用戶端應用程式都能存取可進行 HTTP REST 呼叫。 這種方法類似于 Microsoft Entra 程式設計模型,但用戶端會使用虛擬機器上的端點(與 Microsoft Entra 端點)。

使用 Azure 實例中繼資料服務 (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 實例中繼資料服務的 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 都會與 Azure.Identity 提供 DefaultAzureCredential 支援的程式庫整合。 此類別可讓您輕鬆地搭配 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.AppAuthentication Microsoft.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 參考 App Service 和 KeyVault 搭配 Azure 資源的受控識別 .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,並取得 VM 的相關資訊。 請務必分別以 、 和 <VM-NAME> 取代您的訂用帳戶識別碼、資源組名和虛擬機器名稱。 <RESOURCE-GROUP><SUBSCRIPTION-ID>
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 OK」 狀態是成功的回應,且存取權杖包含在回應主體 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 上未正確設定 Azure 資源的受控識別要求所造成。 如果您需要 VM 設定的協助,請參閱 使用 Azure 入口網站 在 VM 上設定 Azure 資源的受控識別。
access_denied 資源擁有者或授權伺服器拒絕要求。
unsupported_response_type 授權伺服器不支援使用此方法取得存取權杖。
invalid_scope 要求的範圍無效、未知或格式不正確。
500 內部伺服器錯誤 未知 無法從 Active Directory 擷取權杖。 如需詳細資訊,請參閱檔案路徑中的 < 記錄> 確認 VM 已啟用 Azure 資源的受控識別。 如果您需要 VM 設定的協助,請參閱 使用 Azure 入口網站 在 VM 上設定 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 服務與受控識別支援

下一步

  • 若要在 Azure VM 上啟用 Azure 資源的受控識別,請參閱 使用 Azure 入口網站 為 VM 上的 Azure 資源設定受控識別。