PAYATHON 2026

如何从 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:租户 ID
  • CLIENT_ID:应用程序(客户端)ID
  • CLIENT_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 请求会返回认证错误。

备注:内容仅供参考。