如何在 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 使用受控識別。 瞭解更多資訊
安裝 Azure.Identity 套件和其他必要的 Azure SDK 程式庫套件 ,例如 Azure.Security.KeyVault.Secrets 。
使用下列範例程式碼。 您不需要擔心取得權杖。 您可以直接使用 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 參考 。 本節說明如何在程式碼中開始使用程式庫。
將 Microsoft.Azure.Services.AppAuthentication 和 Microsoft.Azure.KeyVault NuGet 套件的 參考新增至您的應用程式。
將下列程式碼新增到您的應用程式:
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 端點受控識別:
- 取得存取權杖。
- 使用存取權杖來呼叫 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 服務,以取得服務清單及其各自的資源識別碼。 |
重要
- IMDS 不打算在 Proxy 後方使用,因此不受支援。 如需如何略過 Proxy 的範例,請參閱 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 資源設定受控識別。