多租户 SaaS 集成 Azure DevOps 时,如何实现不依赖个人用户的身份验证?
推荐方案
使用 Microsoft Entra 服务主体(service principal),通过 OAuth 2.0 client credentials flow 获取应用身份的 access token,再调用 Azure DevOps REST API。这种方式适合由 SaaS 后台事件驱动、无需用户参与的集成。官方身份验证指南
多租户 SaaS 可以在自己的租户中注册一个多租户应用,在各客户租户中创建对应的服务主体,再由客户将该身份加入 Azure DevOps 组织并授权。运行时使用应用凭据,无需依赖某位用户的 refresh token。应用对象与服务主体的关系
这样可以解除对个人账户的依赖。是否需要支付 Azure DevOps 许可证费用,则取决于应用身份使用的访问级别。
为什么现有 refresh token 方案仍然依赖用户
user_impersonation 是委托权限,应用以登录用户的身份访问 Azure DevOps。offline_access 允许应用获取 refresh token,在用户不在线时继续获取 access token,但不会将用户身份转换为应用身份。Microsoft Entra OAuth 接入说明
client credentials flow 使用应用自身的凭据获取 token,无需用户参与,也不签发 refresh token。需要新 token 时,应用再次使用凭据获取即可。client credentials flow 文档
即使换成该用户创建的 PAT,应用仍然依赖这个用户账户。
接入步骤
1. 选择应用注册的管理方式
应用注册可以由各客户独立管理,也可以由 SaaS 统一维护:
| 方式 | 接入安排 |
|---|---|
| 保留每个客户自行注册应用的方式 | 为客户现有应用配置应用凭据,改用 client credentials flow。各客户独立管理应用和凭据 |
| 由 SaaS 提供一个多租户应用 | 在你们的租户中维护应用注册,在客户租户中创建对应的 Enterprise application,也就是本地服务主体 |
第二种方式通常更便于统一维护 SaaS 产品。客户可以通过同意流程或管理员预配创建服务主体,无需各自维护一份应用注册。同一个多租户应用的 Application (client) ID 相同,在不同客户租户中的服务主体 Object ID 则不同。服务主体的多租户模型
首次接入和授权可能需要客户管理员参与,后续后台调用无需使用管理员的登录会话或 refresh token。
2. 将服务主体加入客户的 Azure DevOps 组织
Azure DevOps Services 组织需要先连接到 Microsoft Entra 租户,服务主体也必须存在于该租户中。
客户管理员在 Organization settings > Users > Add users 中添加服务主体,并分配项目和访问级别。通过 API 指定身份时,应使用客户租户中 Enterprise applications 下的服务主体 Object ID。注意区分这个 ID、Application (client) ID 和应用注册对象的 Object ID。添加应用身份的官方步骤
在 Entra 中完成同意或创建 Enterprise application,不会自动授予 Azure DevOps 项目访问权。
3. 在 Azure DevOps 中授予业务权限
Azure DevOps 通过自身的访问级别、组成员关系和资源权限为应用身份授权。这些授权不依靠 Entra 中的 application permissions。应用身份权限配置
工作项集成建议使用专用的 Azure DevOps 安全组,将服务主体加入该组,仅开放所需项目和 Area Path。需要检查的权限包括:
View work items in this nodeEdit work items in this node- 涉及评论编辑时,还需检查
Edit work item comments in this node
各项权限的作用范围和默认分配见工作项权限参考。日常工作项操作无需组织管理员权限。
4. 向客户租户获取应用 token
将请求发送到客户租户的 token endpoint:
https://login.microsoftonline.com/{customerTenantId}/oauth2/v2.0/token
使用 grant_type=client_credentials,scope 可采用 Azure DevOps 官方示例中的值:
https://app.vssps.visualstudio.com/.default
此时不再请求 user_impersonation offline_access。.default 不会自动授予组织内的全部权限,实际访问范围仍由 Azure DevOps 的授权决定。
应用凭据优先选择受支持的 workload identity federation 或证书。使用 client secret 时,需要安全保存并轮换。凭据选择与 token 获取示例
代码示例:使用证书创建工作项
以下 .NET 示例需要 Azure.Identity NuGet 包。证书公钥应已配置到应用注册,运行环境也需要能够访问包含私钥的证书文件。
配置中的 CUSTOMER_TENANT_ID 是客户租户 ID,AZURE_CLIENT_ID 是所选应用注册的 client ID,AZDO_PROJECT_ID 使用目标项目的 GUID。
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using Azure.Core;
using Azure.Identity;
string Required(string name) =>
Environment.GetEnvironmentVariable(name)
?? throw new InvalidOperationException($"Missing {name}.");
var credential = new ClientCertificateCredential(
Required("CUSTOMER_TENANT_ID"),
Required("AZURE_CLIENT_ID"),
Required("AZURE_CLIENT_CERTIFICATE_PATH"));
var token = await credential.GetTokenAsync(
new TokenRequestContext(
new[] { "https://app.vssps.visualstudio.com/.default" }));
var organization = Uri.EscapeDataString(Required("AZDO_ORGANIZATION"));
var projectId = Uri.EscapeDataString(Required("AZDO_PROJECT_ID"));
var patch = new[]
{
new
{
op = "add",
path = "/fields/System.Title",
value = "Task created by SaaS"
}
};
using var client = new HttpClient();
using var request = new HttpRequestMessage(
HttpMethod.Post,
$"https://dev.azure.com/{organization}/{projectId}" +
"/_apis/wit/workitems/$Task?api-version=7.1");
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", token.Token);
request.Content = new StringContent(
JsonSerializer.Serialize(patch),
Encoding.UTF8,
"application/json-patch+json");
using var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
示例假设目标项目支持 Task 类型,且没有需要额外填写的必填字段。创建工作项的请求格式见 Work Items - Create。
更新工作项、添加评论和上传附件时,同样使用 Authorization: Bearer <access_token>,分别调用对应接口:工作项 API、评论 API、附件上传 API。
生产服务应复用 credential 对象,在发送请求前通过 SDK 获取有效 token,由 SDK 管理 token 缓存和重新获取。不要一直复用示例中单次获取的 token。
许可证和 managed identity 的注意事项
服务主体仍需分配访问级别
Stakeholder 是免费的有限访问级别,支持创建和修改工作项。仅操作 Azure Boards 的集成可以先评估 Stakeholder,并验证实际需要的评论、附件及字段操作。使用服务主体并不意味着所有接口都能免费使用。Stakeholder 功能范围
如果需要付费访问级别,应用身份会在每个相应组织中独立消耗许可证。用户的跨组织计费和 Visual Studio 订阅权益不适用于应用身份。应用身份许可证规则
SaaS 部署在 Azure 上,也要处理租户边界
managed identity 适合同租户中的 Azure 托管工作负载,可以省去应用凭据管理。不过,你们自己租户中的 managed identity 不能直接加入连接到另一个客户租户的 Azure DevOps 组织。跨客户租户的 SaaS 接入应使用客户租户中的服务主体来设计。跨租户身份限制
迁移时,可以先保留现有的每客户应用注册,验证应用身份能完成工作项创建、更新、评论和附件操作,再停用后台任务对用户 refresh token 的依赖。之后是否改用统一的多租户应用,可以根据接入管理需要决定。
备注:内容仅供参考。
