共用方式為


如何使用 Azure VM 上 Azure 資源的託管標識來獲取訪問令牌

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

本文提供了各種用於 Token 獲取的代碼和腳本示例。 它還包含有關處理令牌過期和 HTTP 錯誤的指南。

先決條件

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

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

這很重要

  • 本文中的所有範例代碼/文稿都假定用戶端在具有 Azure 資源的託管標識的虛擬機上運行。 使用 Azure 門戶中的虛擬機「連接」功能遠端連接到 VM。 如需在 VM 上為 Azure 資源啟用受控識別的詳細資訊,請參閱使用 Azure 入口網站在 VM 上設定 Azure 資源的受控識別,或其中一篇文章(使用 PowerShell、CLI、範本或 Azure SDK)。

這很重要

  • Azure 資源的託管標識的安全邊界是使用該標識的資源。 在虛擬機上運行的所有代碼/腳本都可以請求和檢索其上可用的任何託管標識的令牌。

概觀

用戶端應用程式可以請求託管標識 僅限應用的訪問令牌 來訪問給定資源。 該令牌 基於 Azure 資源服務主體的託管標識。 因此,用戶端無需在其自己的服務主體下獲取訪問令牌。 該令牌適合在 需要用戶端憑據的服務到服務調用中用作不記名令牌。

連結 說明
使用 HTTP 獲取令牌 Azure 資源令牌終結點的託管標識的協議詳細資訊
使用 Azure.Identity 獲取令牌 使用 Azure.Identity 從 C# 用戶端對 Azure 資源 REST 端點使用受控識別的範例
使用 C 獲取令牌# 使用 HttpClient 從 C# 用戶端對 Azure 資源 REST 端點使用受控識別的範例
使用 Java 獲取令牌 從 Java 用戶端對 Azure 資源 REST 終結點使用託管標識的範例
使用 Go 獲取 Token 從 Go 用戶端對 Azure 資源 REST 終結點使用託管標識的範例
使用 PowerShell 獲取令牌 從 PowerShell 用戶端對 Azure 資源 REST 終結點使用託管標識的範例
使用 CURL 獲取令牌 從 Bash/CURL 用戶端對 Azure 資源 REST 終結點使用託管標識的範例
處理令牌快取 處理過期訪問令牌的指南
錯誤處理 處理從 Azure 資源的託管標識令牌終結點返回的 HTTP 錯誤的指南
Azure 服務的資源 ID 從何處獲取受支援的 Azure 服務的資源 ID

使用 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 查詢字串參數,指示目標資源的App ID URI。 它還顯示在已頒發令牌的 (audience) 聲明中 aud 。 此範例要求一個權杖來存取 Azure 資源管理員,該權杖的應用 ID 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 資源 ID)。 如果您的 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 Identity 客戶端函式庫取得 token

使用 Azure Identity 用戶端函式庫是使用受管理身份的推薦方式。 完成下列步驟:

  1. 安裝 Azure.Identity 包和其他所需的 Azure SDK 庫包,例如 Azure.Security.KeyVault.Secrets

  2. 使用下面的示例代碼。 您無需擔心獲取代幣。 您可以直接使用 Azure SDK 用戶端。 該代碼用於演示如何獲取令牌(如果需要)。

    using Azure.Core;
    using Azure.Identity;
    using Azure.Security.KeyVault.Secrets;
    
    ManagedIdentityCredential credential = new(
        ManagedIdentityId.FromUserAssignedClientId("<managed_identity_client_ID>"));
    
    // Option 1: Explicit token acquisition. Manually fetch the token and convert to a string, if necessary.
    AccessToken accessToken = await credential.GetTokenAsync(
        new TokenRequestContext(["https://vault.azure.net"]));
    string accessTokenString = accessToken.Token;
    
    // Option 2: Implicit token acquisition. Pass the credential object to the Azure service client constructor.
    // Token acquisition is triggered on the GetSecretAsync method call.
    SecretClient client = new(new Uri("https://myvault.vault.azure.net/"), credential);
    KeyVaultSecret secret = await client.GetSecretAsync("MySecret");
    

欲了解更多資訊,請參閱 使用使用者指派管理身份使用系統指派管理身份

使用 C 獲取令牌#

using System;
using System.Net.Http;
using Newtonsoft.Json.Linq;

// Construct HttpClient
var httpClient = new HttpClient
{
    DefaultRequestHeaders =
    {
        { "Metadata", Boolean.TrueString }
    }
};

// Construct URI to call
var resource = "https://management.azure.com/";
var uri = $"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource={resource}";

// Make call
var response = await httpClient.GetAsync(uri);
try
{
    response.EnsureSuccessStatusCode();
}
catch (HttpRequestException)
{
    var error = await response.Content.ReadAsStringAsync();
    Console.WriteLine(error);
    throw;
}

// Parse response using Newtonsoft.Json
var content = await response.Content.ReadAsStringAsync();
var obj = JObject.Parse(content);
var accessToken = obj["access_token"];

Console.WriteLine(accessToken);

使用 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 獲取 Token

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 資源管理員 REST API 並獲取有關 VM 的資訊。 請務必將訂閱 ID、資源群組名稱和虛擬機名稱分別取代為 <SUBSCRIPTION-ID><RESOURCE-GROUP><VM-NAME>
Invoke-RestMethod -Method GET -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
$resource = 'https://management.azure.com'
$response = Invoke-RestMethod -Method GET `
                            -Uri "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=$resource" `
                            -Headers @{ Metadata="true" }
$accessToken = $response.access_token
Write-Host "Access token using a User-Assigned Managed Identity is $accessToken"

# Use the access token to get resource information for the VM
$secureToken = $accessToken | ConvertTo-SecureString -AsPlainText
$vmInfoRest = Invoke-RestMethod -Method GET `
                              -Uri 'https://management.azure.com/subscriptions/<SUBSCRIPTION-ID>/resourceGroups/<RESOURCE-GROUP>/providers/Microsoft.Compute/virtualMachines/<VM-NAME>?api-version=2017-12-01' `
                              -ContentType 'application/json' `
                              -Authentication Bearer `
                              -Token $secureToken
Write-Host "JSON returned from call to get VM info: $($vmInfoRest.content)"

使用 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 Access token using a User-Assigned Managed Identity is $access_token

令牌快取

託管身份子系統緩存令牌,但我們仍建議您在代碼中實現令牌緩存。 您應該為資源指示令牌已過期的情況做好準備。

僅在以下情況下,才會對 Microsoft Entra ID 進行在線調用:

  • 由於 Azure 資源子系統緩存的託管標識中沒有令牌,因此會發生緩存未命中。
  • 緩存的 Token 已過期。

錯誤處理

託管身份終端節點通過 HTTP 回應消息標頭的狀態代碼欄位發出錯誤信號,如 4xx 或 5xx 錯誤:

狀態代碼 錯誤原因 如何處理
404 未找到。 IMDS 終端節點正在更新。 使用 Exponential Backoff 重試。 請參閱下面的指南。
410 IMDS 正在進行更新 IMDS 將在 70 秒內可用
429 請求太多。 已達到 IMDS 限制。 使用 Exponential Backoff 重試。 請參閱下面的指南。
4xx 請求錯誤。 一個或多個請求參數不正確。 不要重試。 檢查錯誤詳細資訊以瞭解更多資訊。 4xx 錯誤是設計時錯誤。
5xx 服務暫時性錯誤。 Azure 資源子系統或 Microsoft Entra ID 的託管標識返回了暫時性錯誤。 等待至少 1 秒後重試是安全的。 如果重試速度過快或過於頻繁,IMDS 和/或 Microsoft Entra ID 可能會返回速率限制錯誤 (429)。
逾時 IMDS 終端節點正在更新。 使用 Exponential Backoff 重試。 請參閱後面的指南。

如果發生錯誤,相應的 HTTP 回應正規將包含包含錯誤詳細資訊的 JSON:

元素 說明
錯誤 錯誤標識碼。
錯誤描述 錯誤的詳細描述。 錯誤描述可能隨時更改。 不要編寫基於錯誤描述中的值進行分支的代碼。

HTTP 回應參考

本節記錄了可能的錯誤回應。 “200 OK”狀態表示回應成功,訪問令牌包含在回應正文 JSON 的 access_token 元素中。

狀態代碼 錯誤 錯誤說明 解決方法
400 錯誤的請求 無效資源 AADSTS50001:在名為 <TENANT-ID> 的租戶中找不到名為 <URI> 的應用程式。 此消息顯示租戶管理員是否尚未安裝應用程式或沒有租戶使用者同意它。 您可能已將身份驗證請求發送到錯誤的租戶。\ (僅限 Linux)
400 錯誤的請求 錯誤請求_102 未指定必需的元數據標頭 Metadata請求中缺少請求標頭欄位,或者格式不正確。 該值必須指定為 true,全部小寫。 有關範例,請參閱前面的 REST 部分中的“示例請求”。
401 未經授權 unknown_source 未知源 <URI> 驗證 HTTP GET 請求 URI 的格式是否正確。 該 scheme:host/resource-path 部份必須指定為 http://localhost:50342/oauth2/token。 有關範例,請參閱前面的 REST 部分中的“示例請求”。
無效請求 請求缺少必需的參數、包含無效的參數值、多次包含參數或格式不正確。
未授權的客戶端 用戶端無權使用此方法請求訪問令牌。 由未正確配置 Azure 資源的託管標識的 VM 上的請求引起。 如果您需要 VM 配置方面的説明,請參閱 使用 Azure 門戶為 VM 上的 Azure 資源配置託管標識
拒絕存取 資源擁有者或授權伺服器拒絕了該請求。
不支援的回應類型 授權伺服器不支援使用此方法獲取訪問令牌。
範圍無效 請求的範圍無效、未知或格式錯誤。
500 內部伺服器錯誤 未知 無法從 Active Directory 檢索令牌。 有關詳細資訊,請參閱文件路徑<中的>日誌 驗證 VM 是否啟用了 Azure 資源的託管標識。 如果您需要 VM 配置方面的説明,請參閱 使用 Azure 門戶為 VM 上的 Azure 資源配置託管標識

此外,請驗證 HTTP GET 請求 URI 的格式是否正確,尤其是查詢字串中指定的資源 URI。 有關示例,請參閱前面的 REST 部分中的「示例請求」,有關服務及其各自資源 ID 的清單,請參閱 支援 Microsoft Entra 身份驗證的 Azure 服務

這很重要

  • IMDS 不打算在代理後面使用,因此不支持這樣做。 有關如何繞過代理的範例,請參閱 Azure 實例元數據範例

重試指南

如果您收到 404、429 或 5xx 錯誤代碼,請重試(請參閱 錯誤處理)。 如果您收到 410 錯誤,則表明 IMDS 正在進行更新,並且將在最多 70 秒內可用。

限制適用於對 IMDS 終端節點的調用次數。 當超過限制閾值時,IMDS 終端節點會在限制生效期間限制任何進一步的請求。 在此期間,IMDS 終端節點返回 HTTP 狀態代碼 429(“請求過多”),並且請求失敗。

對於重試,我們建議採用以下策略:

重試策略 設定 價值 運作方式
指數回退 重試計數
最小回退
最大回退
Delta 回退
首次快速重試
5
0 秒
60 秒
2 秒
假的
嘗試 1 - 延遲 0 秒
嘗試 2 - 延遲 ~2 秒
嘗試 3 - 延遲 ~6 秒
嘗試 4 - 延遲 ~14 秒
嘗試 5 - 延遲 ~30 秒

Azure 服務的資源 ID

有關支援 Azure 資源的託管標識的資源清單,請參閱 支援託管標識的 Azure 服務

後續步驟