Azure 资源的托管标识是 Microsoft Entra ID 的一项功能。 支持 Azure 资源的托管标识的每个 Azure 服务 都受其自己的时间线限制。 在开始之前,请务必查看资源的托管标识的 可用性 状态以及 已知问题

Azure 资源的托管标识在 Microsoft Entra ID 中为 Azure 服务提供了一个自动托管标识。 可以使用此标识向支持 Microsoft Entra 身份验证的任何服务进行身份验证,这样就无需在代码中插入凭据了。

本文提供了用于获取令牌的各种代码和脚本示例。 其中还包含有关处理令牌过期和 HTTP 错误的指南。

  • 如果不熟悉 Azure 资源功能的托管标识,请参阅此 概述 。 如果没有 Azure 帐户,请在继续操作前 注册免费帐户
  • 如果打算使用本文中的 Azure PowerShell 示例,请务必安装最新版本的 Azure PowerShell

  • 本文中的所有示例代码/脚本假设客户端使用 Azure 资源的托管标识在虚拟机上运行。 在 Azure 门户中使用虚拟机的“连接”功能远程连接到 VM。 有关在 VM 上启用 Azure 资源的托管标识的详细信息,请参阅 使用 Azure 门户在 VM 上配置 Azure 资源的托管标识 ,或有关在不同工具(使用 PowerShell、CLI、模板或 Azure SDK)中执行此操作的文章之一。
  • Azure 资源托管标识的安全边界是使用该标识的资源。 虚拟机上运行的所有代码/脚本都可以请求和检索虚拟机上可用的任何托管标识的令牌。
  • 客户端应用程序可以请求托管标识 仅限应用的访问令牌 用于访问给定的资源。 令牌 基于 Azure 资源的托管标识服务主体 。 因此,客户端无需在自己的服务主体下获取访问令牌。 该令牌适合在 需要客户端凭据的服务到服务调用 中用作持有者令牌。

    使用用于 .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 服务的资源 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
    resource
    一个查询字符串参数,表示目标资源的应用 ID URI。 它也会显示在所颁发令牌的 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 标识客户端库获取令牌

    建议使用 Azure 标识客户端库来使用托管标识。 所有 Azure SDK 都与提供对 DefaultAzureCredential 支持的 Azure.Identity 库集成。 使用此类可以轻松地将托管标识与 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.AppAuthenticationMicrosoft.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 参考以及将应用服务和 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";
        // 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 获取令牌

    通过 Java 使用此 JSON 库检索令牌。

    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 资源管理器 REST API 并获取有关 VM 的信息。 请务必将 <SUBSCRIPTION-ID><RESOURCE-GROUP><VM-NAME> 分别替换为自己的订阅 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 错误的形式指示错误:

    服务出现 5xx 暂时性错误。 Azure 资源子系统或 Microsoft Entra ID 的托管标识返回了暂时性错误。 至少等待 1 秒后可以安全重试。 如果重试过快或过于频繁,IMDS 和/或 Microsoft Entra ID 可能会返回速率限制错误 (429)。 timeout 正在更新 IMDS 终结点。 使用指数补偿重试。 请参阅下面的指南。

    如果发生错误,相应的 HTTP 响应正文将包含 JSON 和错误详细信息:

    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 请求中缺少必需的参数、包含无效的参数值、多次包含某个参数,或格式不正确。

    建议在收到 404、429 或 5xx 错误代码后重试(请参阅上述错误处理)。 如果收到 410 错误,则表示 IMDS 正在进行更新,并且将在最长 70 秒内可用。

    限制适用于对 IMDS 终结点所做的调用次数。 当超出限制阈值时,IMDS 终结点在限制有效时限制任何后续请求。 在此期间,IMDS 终结点会返回 HTTP 状态码 429(“请求过多”),并且请求失败。

    若要重试,建议使用以下策略:

  •