如何从 Azure 获取 non-interactive client 的 access token
结论
non-interactive client 应使用 OAuth 2.0 的 Client Credentials Grant,不应使用 Authorization Code Grant。请求 token 时需要提供:
- Microsoft Entra tenant ID
- 应用的
client_id - 客户端密钥
client_secret grant_type=client_credentials- Resource Usage API 所属资源的标识
subscription_id 不参与 token 请求,而是用于后续 Resource Usage API 的请求 URL。
前置配置
先在 Microsoft Entra ID 中注册应用并创建客户端密钥,取得以下信息:
TENANT_ID:租户 IDCLIENT_ID:应用程序(客户端)IDCLIENT_SECRET:客户端密钥值
然后将应用对应的 service principal 授权到目标订阅、资源组或计费范围。所需角色由调用的 API 决定:
- 查询普通 Azure Resource Manager 资源时,通常可以从
Reader开始。 - 查询用量、成本或账单数据时,可能还需要
Cost Management Reader、Billing Reader,或计费账户范围内的相应权限。 - 只在 Entra ID 中注册应用,不会自动获得订阅数据的访问权限。
使用 v1 token endpoint 获取 token
问题中的 /oauth2/token 是 v1 endpoint,需要按以下格式发送表单数据:
TENANT_ID="your-tenant-id"
CLIENT_ID="your-client-id"
CLIENT_SECRET="your-client-secret"
curl --request POST \
"https://login.microsoftonline.com/${TENANT_ID}/oauth2/token" \
--header "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=${CLIENT_ID}" \
--data-urlencode "client_secret=${CLIENT_SECRET}" \
--data-urlencode "resource=https://management.azure.com/"
请求成功后会返回类似下面的 JSON:
{
"token_type": "Bearer",
"expires_in": "3599",
"access_token": "eyJ..."
}
原请求使用了 -d "",发送的是空请求正文,所以 Entra ID 首先提示缺少必需的 grant_type。但只补充 grant_type 仍然不够。缺少 client_id、client_secret 或 resource 时,请求同样无法成功。
token endpoint 中的 TENANT_ID 是 Microsoft Entra 租户 ID,不是 Azure subscription ID:
https://login.microsoftonline.com/{tenant-id}/oauth2/token
调用 Resource Usage API
取得 access_token 后,将其放入 Authorization 请求头。subscription ID 在这一步才会出现在 API 地址中:
ACCESS_TOKEN="eyJ..."
SUBSCRIPTION_ID="your-subscription-id"
curl --request GET \
"https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/providers/Microsoft.Commerce/usageAggregates?api-version=<supported-api-version>&reportedStartTime=<start-time>&reportedEndTime=<end-time>" \
--header "Authorization: Bearer ${ACCESS_TOKEN}" \
--header "Accept: application/json"
api-version、时间格式和具体路径应以实际使用的 Azure 用量或计费 API 为准。旧版 Microsoft.Commerce/usageAggregates 接口与较新的 Cost Management、Consumption 或 Billing API 并不完全相同,参数不能直接混用。
使用 v2 token endpoint
使用较新的 Microsoft identity platform v2 endpoint 时,应传递 scope,而不是 resource:
curl --request POST \
"https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token" \
--header "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=${CLIENT_ID}" \
--data-urlencode "client_secret=${CLIENT_SECRET}" \
--data-urlencode "scope=https://management.azure.com/.default"
不要混用这两种格式:
/oauth2/token使用resource=https://management.azure.com//oauth2/v2.0/token使用scope=https://management.azure.com/.default
常见错误
invalid_client
一般表示 client_id、client_secret 或 tenant ID 有误。客户端密钥必须使用创建时显示的 secret value,不能使用 secret ID。
invalid_resource 或 token audience 不正确
调用 https://management.azure.com/ 下的 Azure Resource Manager API 时,token 的目标资源通常是:
https://management.azure.com/
不要将 subscription ID 填入 resource 或 scope。
API 返回 401 Unauthorized
检查 token 是否过期,以及 token 的 aud 是否与目标 API 匹配。同时确认 Authorization 请求头使用以下格式:
Authorization: Bearer <access-token>
API 返回 403 Forbidden
这通常表示身份验证已经成功,但 service principal 没有访问目标订阅或计费范围的权限。此时需要检查角色分配和授权范围,并确认该计费 API 是否要求额外的账单访问权限。
安全注意事项
不要将客户端密钥直接写入源码、提交到 Git 或输出到日志。生产环境应优先使用环境变量、密钥管理服务、证书凭据,或在 Azure 托管环境中使用 Managed Identity。客户端密钥过期后需要及时轮换,否则 token 请求会返回认证错误。
备注:内容仅供参考。
