如何以编程方式永久删除Azure App Registration的API权限
问题原因
你当前的代码仅修改了应用注册(Application)对象上的requiredResourceAccess属性,这个属性只用于定义「应用声明需要配置哪些权限」,不会删除租户内已经实际授予给该应用的权限授权记录。你看到权限移动到「为[TENANT]授予的其他权限」分类,本质是权限配置条目被删除了,但历史授权记录仍然绑定在应用对应的服务主体(Service Principal)上没有被清理。
要永久删除API权限,必须同时完成配置移除和授权记录删除两个操作。
操作步骤
- 第一步:移除应用注册上的权限配置,即你现有代码已经实现的逻辑,更新
requiredResourceAccess属性,移除不需要的权限条目 - 第二步:删除对应服务主体上的已生效授权记录,这是你当前缺失的核心步骤:
- 如果要删除的是委托权限(Delegated Permission),需要删除对应的
oAuth2PermissionGrant对象 - 如果要删除的是应用程序权限(Application Permission),需要删除对应的
appRoleAssignment对象
- 如果要删除的是委托权限(Delegated Permission),需要删除对应的
完整实现代码(C# Microsoft Graph SDK)
// 配置参数 var appObjectId = "{你的应用注册对象ID}"; var appClientId = "{你的应用注册Application (client) ID}"; var targetResourceAppId = "{要删除权限所属API的客户端ID,即你代码中的proxyClientId}"; var targetPermissionId = "{要删除的权限ID}"; var isDelegatedPermission = true; // 删除应用权限时将该值设为false // 1. 移除应用注册上的权限配置 var app = await _graphClient.Applications[appObjectId].Request().GetAsync(); var updatedRequiredAccess = new List<RequiredResourceAccess>(); foreach (var resource in app.RequiredResourceAccess) { if (resource.ResourceAppId != targetResourceAppId) { updatedRequiredAccess.Add(resource); continue; } // 同资源下仅删除单个权限时,过滤掉目标权限即可,无需移除整个资源条目 var remainingPermissions = resource.ResourceAccess.Where(p => p.Id != targetPermissionId).ToList(); if (remainingPermissions.Any()) { updatedRequiredAccess.Add(new RequiredResourceAccess { ResourceAppId = resource.ResourceAppId, ResourceAccess = remainingPermissions }); } } await _graphClient.Applications[appObjectId].Request().UpdateAsync(new Application { RequiredResourceAccess = updatedRequiredAccess }); // 2. 删除服务主体上的实际授权记录 // 先查询应用对应的服务主体 var spResponse = await _graphClient.ServicePrincipals.Request() .Filter($"appId eq '{appClientId}'") .GetAsync(); var servicePrincipal = spResponse.FirstOrDefault(); if (servicePrincipal == null) throw new Exception("未找到应用对应的服务主体"); if (isDelegatedPermission) { // 处理委托权限删除 var grants = await _graphClient.Oauth2PermissionGrants.Request() .Filter($"clientId eq '{servicePrincipal.Id}' and resourceId eq (select id from servicePrincipals where appId eq '{targetResourceAppId}')") .GetAsync(); foreach (var grant in grants) { var scopes = grant.Scope.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList(); // 注意:oAuth2PermissionGrant中存储的是权限scope值,若按ID匹配请先查询资源服务主体的oauth2PermissionScope列表获取对应值 if (scopes.Contains("{替换为要删除的委托权限scope值}")) { scopes.Remove("{替换为要删除的委托权限scope值}"); if (!scopes.Any()) { // 授权条目下无剩余权限时直接删除整个条目 await _graphClient.Oauth2PermissionGrants[grant.Id].Request().DeleteAsync(); } else { // 有剩余权限时更新scope列表 grant.Scope = string.Join(' ', scopes); await _graphClient.Oauth2PermissionGrants[grant.Id].Request().UpdateAsync(grant); } } } } else { // 处理应用程序权限删除 var assignments = await _graphClient.ServicePrincipals[servicePrincipal.Id].AppRoleAssignments.Request() .Filter($"resourceId eq (select id from servicePrincipals where appId eq '{targetResourceAppId}') and appRoleId eq {targetPermissionId}") .GetAsync(); foreach (var assignment in assignments) { await _graphClient.ServicePrincipals[servicePrincipal.Id].AppRoleAssignments[assignment.Id].Request().DeleteAsync(); } }
注意:执行以上操作需要给Graph客户端授予
Application.ReadWrite.All等具备应用、服务主体读写能力的目录权限,操作完成后权限会被彻底移除,不会再出现在其他已授予权限列表中。
内容的提问来源于stack exchange,提问作者Ratheesh
相关产品推荐
相关产品推荐

