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 通过 C# 客户端为 Azure 资源 REST 终结点使用托管标识的示例 |
| 使用 C# 获取令牌 | 使用 HttpClient 通过 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.chinacloudapi.cn/' 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 |
一个查询字符串参数,表示目标资源的应用 ID URI。 它也会显示在所颁发令牌的 aud(受众)声明中。 本示例请求一个用于访问 Azure 资源管理器的、应用 ID URI 为 https://management.chinacloudapi.cn/ 的令牌。 |
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.chinacloudapi.cn/",
"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.Identity 包和其他所需的 Azure SDK 库包,如 Azure.Security.KeyVault.Secrets。
使用下面的示例代码。 无需为获取令牌担心。 你可以直接使用 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.cn"])); 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.cn/"), 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.chinacloudapi.cn/";
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 获取令牌
通过 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.chinacloudapi.cn/");
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.chinacloudapi.cn/")
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-RestMethod -Method GET -Uri 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.chinacloudapi.cn%2F' -Headers @{Metadata="true"}
有关如何分析响应中的访问令牌的示例:
# Get an access token for managed identities for Azure resources
$resource = 'https://management.chinacloudapi.cn'
$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.chinacloudapi.cn/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.chinacloudapi.cn%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.chinacloudapi.cn%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 资源托管标识子系统缓存中没有令牌,因此会发生缓存失误。
- 缓存令牌已过期。
错误处理
托管标识终结点通过 HTTP 响应消息标头的状态代码字段,以 4xx 或 5xx 错误的形式指示错误:
| 状态代码 | 错误原因 | 处理方式 |
|---|---|---|
| 404 未找到。 | 正在更新 IMDS 终结点。 | 使用指数补偿重试。 请参阅下面的指南。 |
| 410 | IMDS 正在进行更新 | IMDS 将在 70 秒内可用 |
| 429 请求过多 | 达到 IMDS 节流限制。 | 使用指数补偿重试。 请参阅下面的指南。 |
| 请求中出现 4xx 错误。 | 一个或多个请求参数不正确。 | 请勿重试。 查看错误详细信息了解更多信息。 4xx 错误属于设计时错误。 |
| 服务出现 5xx 暂时性错误。 | Azure 资源托管标识子系统或 Microsoft Entra ID 返回了暂时性错误。 | 至少等待 1 秒后可以安全重试。 如果重试过快或过于频繁,IMDS 和/或 Microsoft Entra ID 可能会返回速率限制错误 (429)。 |
| 超时 | 正在更新 IMDS 终结点。 | 使用指数补偿重试。 请参阅后面的指南。 |
如果发生错误,相应的 HTTP 响应正文将包含 JSON 和错误详细信息:
| 元素 | 说明 |
|---|---|
| 错误 | 错误标识符。 |
| 错误描述 | 错误的详细说明。 错误说明随时可能更改。 请不要编写会根据错误说明中的值生成分支片段的代码。 |
HTTP 响应参考
本部分介绍可能的错误响应。 “200 OK”状态表示成功的响应,访问令牌包含在响应正文 JSON 的 access_token 元素中。
| 状态代码 | 错误 | 错误说明 | 解决方案 |
|---|---|---|---|
| 400 错误的请求 | 资源无效 | AADSTS50001:在名为 <TENANT-ID> 的租户中找不到名为 <URI> 的应用程序。 如果租户管理员尚未安装应用程序,或没有租户用户同意该应用程序,则会显示此消息。 可能将身份验证请求发送给了错误的租户。\ | (仅限 Linux) |
| 400 错误的请求 | 错误请求_102 (bad_request_102) | 未指定必需的元数据标头 | 请求中缺少 Metadata 请求标头字段,或者该字段的格式不正确。 必须将该值指定为 true(全小写)。 有关示例,请参阅前面 REST 部分中的“示例请求”。 |
| 401 未授权 | 未知来源 | 未知源 <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(“请求过多”),并且请求失败。
若要重试,建议使用以下策略:
| 重试策略 | 设置 | 价值观 | 工作原理 |
|---|---|---|---|
| ExponentialBackoff(指数退避) | 重试计数 最小回退 最大回退 增量回退 首次快速重试 |
5 0 秒 60 秒 2 秒 否 |
第 1 次尝试 - 延迟 0 秒 第 2 次尝试 - 约延迟 2 秒 第 3 次尝试 - 约延迟 6 秒 第 4 次尝试 - 约延迟 14 秒 第 5 次尝试 - 约延迟 30 秒 |
Azure 服务的资源 ID
如需支持 Azure 资源托管标识的资源列表,请参阅支持托管标识的 Azure 服务。
后续步骤
- 若要启用 Azure VM 上的 Azure 资源的托管标识,请参阅使用 Azure 门户在 VM 上配置 Azure 资源的托管标识。