You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Microsoft Entra ID应用用户模拟:跨应用及Azure DevOps API访问问题

问题分析与解决方案

核心问题

你拿到的访问令牌受众(aud声明)是App1的客户端ID,而Azure DevOps API只接受受众为自身固定资源ID(499b84ac-1321-427f-aa17-267ca6975798)的令牌,所以直接传递App1的令牌会被拒绝并返回登录页面。另外,你需要使用**OAuth 2.0代表流(OBO Flow)**来实现后端API代表用户调用Azure DevOps API,这是你之前遗漏的核心流程。

必要配置检查

  • 确认App1添加的Azure DevOps权限是委托权限下的user_impersonation,而非应用权限。代表用户操作必须用委托权限,应用权限是应用自身身份的操作,不关联用户身份。
  • 确保该委托权限已获得管理员同意。后端API场景下,管理员同意是强制要求,否则无法获取关联用户的有效令牌。

正确实现流程

你需要将用户提供给App1的令牌兑换为针对Azure DevOps的访问令牌,具体步骤如下:

1. 获取用户针对App1的有效令牌

当用户访问App1时,App1需先获取用户的访问令牌——该令牌的受众是App1,且包含scp声明(表示用户授予App1的委托权限)。这个令牌通常来自前端客户端的身份验证结果,或其他调用App1的客户端传递的用户令牌。

2. 使用OBO流兑换Azure DevOps令牌

利用Azure AD的代表流,App1以自身托管身份(无需客户端密钥)为身份,结合用户的令牌,向Azure AD请求针对Azure DevOps资源的访问令牌。

代码示例(Python,基于azure-identity)

from azure.identity import OnBehalfOfCredential
import requests

# 用户访问App1时提供的访问令牌
user_access_token = "<user-access-token-for-app1>"

# 初始化代表流凭据
obo_credential = OnBehalfOfCredential(
    tenant_id="<你的租户ID>",
    client_id="<App1的客户端ID>",
    client_credential=None,  # 托管身份无需客户端密钥,自动使用托管身份完成身份验证
    user_assertion=user_access_token
)

# 请求Azure DevOps的访问令牌,资源ID固定为499b84ac-1321-427f-aa17-267ca6975798
devops_token = obo_credential.get_token("499b84ac-1321-427f-aa17-267ca6975798/.default")

# 调用Azure DevOps API示例
headers = {
    "Authorization": f"Bearer {devops_token.token}",
    "Content-Type": "application/json"
}
response = requests.get(
    "https://dev.azure.com/<你的组织名>/_apis/projects?api-version=7.1-preview.4",
    headers=headers
)
print(response.json())

关键注意事项

  • 不要使用az account get-access-token --resource "api://<app1-client-id>"获取的令牌,该令牌仅适用于访问App1,无法直接用于Azure DevOps。
  • 托管身份仅用于App1自身与Azure服务(如KeyVault)的交互,代表用户调用Azure DevOps时,核心依赖OBO流,托管身份在这里作为App1的身份发起令牌兑换请求。
  • 用户的令牌必须有效且包含scp声明,否则OBO流会执行失败。

内容的提问来源于stack exchange,提问作者captainjack42

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 05:22:45