通过Azure APIM为Claude Code添加企业级管控:Entra ID鉴权、分级预算、每人速率限制、日预算和用量计量,实现安全可控的AI编程规模化部署。

在 Foundry 上运行 Claude Code 解决了一个开发者的凭证问题:执行 az login 即可接入,全程无需任何 API key。
但扩展到团队后就暴露了另一个问题。直连 Foundry 意味着每个开发者都需要在资源上拥有 Cognitive Services User 角色,而这带来的问题是:没有速率限制、没有预算、没有分级、没有细粒度用量数据——你只能看到 $4,000 的 token 被消耗了,但无法得知是谁花的。
本文在前方放置了一个 Azure API Management AI 网关。开发者最终不再持有任何 Foundry 角色。
以下所有内容均在 2026 年 8 月 13 日构建并执行完成:APIM Basic v2、Claude Code v2.1.223、East US 2 区域的 claude-sonnet-5 和 claude-opus-5。
跳过手动步骤:整套方案已打包为一键加速器——github.com/naveenneog/claude-code-foundry-gateway。下方步骤 1–7 即为该加速器所自动化的内容,建议在运行前先阅读理解。

这是承载整篇文章的关键细节,而我是通过拦截真实流量而非假设得出的结论。
Claude Code 的 Foundry 模式并不自行创建凭证。它调用 Azure SDK 的 DefaultAzureCredential,因此请求携带的是开发者自己的 Entra ID token:
{
"aud": "https://cognitiveservices.azure.com",
"oid": "43cc5304-...",
"upn": "naveen.g@contoso.com",
"appid": "04b07795-8ddb-461a-bbee-02f9e1bf7b46"
}
身份是不可伪造的。oid 由 Entra 签名。开发者无法冒充同事,也不存在可共享的凭证,因为压根就没有凭证可共享。
无需任何客户端 auth 工作。没有应用注册、没有自定义 audience、没有 device-code flow。
离场(offboarding)是零成本的。将人从组中移除即可停止访问。端点侧无需任何撤销操作。
Claude Code 还会发送一个 x-claude-code-session-id 请求头,这使其成为一个有用的指标维度。
SKU 的选择比本文任何其他内容都更重要。APIM 的 llm-* 策略只在 v2 层级上解析 Anthropic Messages API 形状。在经典 Developer/Basic/Standard/Premium 层上,策略可以愉快地应用但 token 计数永远为 0——配额永远不会触发,你会以为自己在被治理而实际上并没有。
az apim create 不支持 v2,所以用 ARM/Bicep 部署:
az deployment group create -g rg-claude-gateway `
--template-file infra/main.bicep `
--parameters foundryAccountName=<foundry> publisherEmail=<you@contoso.com>
Basic v2 大约 4 分钟完成配置;经典层级需要 30–45 分钟。
模板启用了一个系统分配的托管身份——即那个将代表网关访问 Foundry 的身份。
$apimMi = az apim show -n <apim> -g <rg> --query identity.principalId -o tsv
$scope = az cognitiveservices account show -n <foundry> -g <rg> --query id -o tsv
az role assignment create `
--assignee-object-id $apimMi --assignee-principal-type ServicePrincipal `
--role "Cognitive Services User" --scope $scope
然后是真正关闭绕过通道的那一步:
az role assignment delete --assignee <developer-oid> `
--role "Cognitive Services User" --scope $scope
只要开发者仍持有该角色,他们就可以让 Claude Code 直连 Foundry、跳过下方所有控制。只有将该角色只分配给网关才能堵住这条路。
az ad group create --display-name claude-code-standard --mail-nickname claude-code-standard
az ad group create --display-name claude-code-premium --mail-nickname claude-code-premium
授权应该放在 Entra,因为 joiner/mover/leaver(入职/转岗/离职)的流程本就在那里运行。将某人加入 claude-code-premium 是你的身份团队可以执行、审计和审查的操作——而配置文件中的 allowlist 会漂移,且无人负责。
az apim api create -g <rg> --service-name <apim> `
--api-id claude-foundry --path claude `
--service-url "https://<foundry>.services.ai.azure.com/anthropic" `
--protocols https --subscription-required false
az apim api operation create -g <rg> --service-name <apim> `
--api-id claude-foundry --operation-id messages `
--display-name "Create Message" --method POST --url-template "/v1/messages"
--subscription-required false 是刻意为之:授权来自 Entra token,而非 APIM 订阅密钥。Claude Code 没有可靠的方式发送自定义 key header,而共享密钥会破坏按人归属的能力。
配额限制存在于命名值中,这样修改预算只需编辑配置而非重新部署:
tpm-standard=20000 quota-standard=500000
tpm-premium=80000 quota-premium=5000000
calls-per-minute=120
策略本身,按顺序排列:
<validate-azure-ad-token tenant-id="{{tenant-id}}" output-token-variable-name="jwt"
failed-validation-httpcode="401">
<audiences>
<audience>https://cognitiveservices.azure.com</audience>
</audiences>
</validate-azure-ad-token>
<set-variable name="userId" value="@(((Jwt)context.Variables["jwt"]).Claims.GetValueOrDefault("oid",""))" />
<set-variable name="tier" value="@{
var oid = "," + (string)context.Variables["userId"] + ",";
if (("{{allow-premium}}").Contains(oid)) { return "premium"; }
if (("{{allow-standard}}").Contains(oid)) { return "standard"; }
return "denied";
}" />
<llm-token-limit counter-key="@((string)context.Variables["userId"])"
tokens-per-minute="{{tpm-standard}}" estimate-prompt-tokens="false"
retry-after-header-name="Retry-After"
remaining-tokens-header-name="x-ratelimit-remaining-tokens"
tokens-consumed-header-name="x-tokens-consumed" />
<llm-emit-token-metric namespace="claudecode">
<dimension name="User" value="@((string)context.Variables["userUpn"])" />
<dimension name="Tier" value="@((string)context.Variables["tier"])" />
</llm-emit-token-metric>
<authentication-managed-identity resource="https://cognitiveservices.azure.com"
output-token-variable-name="msi-token" />
<set-header name="Authorization" exists-action="override">
<value>@("Bearer " + (string)context.Variables["msi-token"])</value>
</set-header>
值得知道四件事:
audience 是 https://cognitiveservices.azure.com——这是 Claude Code 请求 token 的对象,所以这也是网关必须接受的。没有应用注册要求。
estimate-prompt-tokens="false" 从响应中计费实际用量。
XML 注释中不能包含 --。APIM 会拒绝该策略,但报错信息从不提及是注释的问题。
开发者的 token 被丢弃而不转发。Foundry 只能看到网关身份。
allowlist 中每个 object id 前后添加哨兵逗号使 contains() 变为精确匹配,这样一个 id 不会部分匹配到另一个。
网关无法从调用者的 token 中读取组 membership: Cognitive Services audience 是一个第一方 Microsoft 资源,其 token 没有可配置的 groups claim。有两个选项:
我在 Graph grant 上遇到了 Authorization_RequestDenied——它需要 Privileged Role Administrator——所以加速器采用了同步方案并记录了 Graph 升级方式。
./scripts/Sync-ClaudeAccess.ps1 -ApimName <apim> -ResourceGroup <rg>
服务主体在组内对委托 token 不可见(没有 Application.Read.All),所以 CI 身份通过 -AdditionalPremiumOids 显式传入。
两个设置项,两者在你遗漏时都会静默失败:
APIM 诊断需要 metrics: true,否则 llm-emit-token-metric 什么都发不出,metric namespace 永远不会出现
Application Insights 需要 CustomMetricsOptedInType: WithDimensions,否则 metric 会以裸总量到达,没有 User 维度分解——而这恰恰是 chargeback 所需的部分
naveen.g@contoso.com 831 tokens
service-principal 728 tokens
Azure CLI 的 az monitor metrics list 对自定义 namespace 去掉 --namespace 参数并报告 "metric not found"。改为查询 REST API。
{
"env": {
"CLAUDE_CODE_USE_FOUNDRY": "1",
"ANTHROPIC_FOUNDRY_BASE_URL": "https://<apim>.azure-api.net/claude",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-5",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "claude-opus-5",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-sonnet-5"
},
"availableModels": ["claude-sonnet-5", "claude-opus-5"],
"enforceAvailableModels": true
}
ANTHROPIC_FOUNDRY_BASE_URL 和 ANTHROPIC_FOUNDRY_RESOURCE 互斥。同时设置两者会以 baseURL and resource are mutually exclusive 报错。迁移到网关后删除 resource 变量。
这一个文件同时覆盖 CLI 和 VS Code 扩展。面板直接打开即可使用 prompt——无登录步骤,因为 Entra 凭证已经被解析——且流式输出正常通过网关:

要阻止开发者编辑自己的设置,将同样的 JSON 作为托管设置推送:Windows 上通过 Intune 或组策略推送到 HKLM\SOFTWARE\Policies\ClaudeCode,macOS 上推送到 /Library/Application Support/ClaudeCode/managed-settings.json,Linux 上推送到 /etc/claude-code/managed-settings.json。这些也允许你强制 availableModels 并阻止 bypassPermissions。
az ad group member add --group claude-code-standard `
--member-id (az ad user show --id alice@contoso.com --query id -o tsv)
./scripts/Sync-ClaudeAccess.ps1 -ApimName <apim> -ResourceGroup <rg>
Alice 然后运行 az login,放入 settings 文件,就可以工作了。无 key、无 Foundry 角色,且从她的第一个请求起就出现在 chargeback 中。
Guest 注意事项:B2B guest(带有 #EXT# 的 UPN)必须使用 az login --tenant <tenant-id>。普通 az login 会让他们进入自己的 home tenant,网关返回 401。这在我给同事入职时踩过坑。
以上全部已打包为解决方案加速器:
github.com/naveenneog/claude-code-foundry-gateway
git clone https://github.com/naveenneog/claude-code-foundry-gateway
cd claude-code-foundry-gateway
az login
./deploy.ps1
无参数执行时,它会发现已部署 Claude 的 Foundry 账户,将 sonnet/opus/haiku 别名映射到实际存在的部署,部署 APIM + Log Analytics + Application Insights,创建 API 和策略,授予网关身份其角色,创建 Entra 组,同步 membership,并生成你交给开发者的 settings.json。
./deploy.ps1 -WhatIf 预览而不做任何更改。门户路径有 Deploy to Azure 按钮,有 Show-Governance.ps1 验证全部四项控制,以及一个检查器代理展示 Claude Code 实际发送的内容——这正是上文身份模型得以通过拦截而非假设建立的方式。
Basic v2 是能够强制执行 Anthropic token 预算的最便宜层级。如果这对试点项目来说仍太贵,先直连 Foundry,等第二个团队加入时再加网关。
这个网关为你提供了直连访问无法获得的四项能力:授权、预算、限流和归属。代价是一个 APIM 实例和多一跳延迟。
对于单个开发者,这个交易不值得。对于一个团队——尤其是当"谁花了那 $4,000?"是迟早会被问到的场景时——这就是一个可以推广的工具和一个需要一直解释的工具之间的区别。
我没有预料到的部分:这一切都不需要改变开发者的工作方式。他们仍然运行 az login 和 claude。治理完全在平台侧。
💻 加速器:https://github.com/naveenneog/claude-code-foundry-gateway
📘 首发:在 Foundry 上配置 Claude Code:分步设置指南
📗 Microsoft Learn — APIM llm-token-limit:https://learn.microsoft.com/en-us/azure/api-management/llm-token-limit-policy
📗 Microsoft Learn — llm-emit-token-metric:https://learn.microsoft.com/en-us/azure/api-management/llm-emit-token-metric-policy