使用MS Graph复制DriveItem后遇Invalid Audience授权错误咨询
向MS Graph发起Copy请求后,需要获取对应的driveItem。按照文档要求,处理请求头Location获取复制流程的监控地址,使用AsyncMonitor<DriveItem>及其PollForOperationCompletionAsync方法,但复制完成后收到authorization error: Invalid Audience错误。推测原因是复制成功后asyncMonitor重定向到了SharePoint REST API的资源地址(格式为https://{tenant-name}.sharepoint.com/_api/v2.0/drives/{drive-id}/items/{driveItem})。
需要明确两个问题:
- 为何使用MS Graph API却会返回SharePoint REST API的资源?
- 是否需要进行令牌交换等操作?
代码示例:
var copyResponse = await client.Sites[siteId].Lists[listId].Drive.Items[file.DriveItemId] .Copy(file.Name, reference) .Request() .PostResponseAsync(); var locationHeader = copyResponse.HttpHeaders.Location; if (locationHeader == null) { return null; } if (!locationHeader.IsAbsoluteUri) { locationHeader = new Uri(client.BaseUrl + locationHeader.OriginalString); } IProgress<AsyncOperationStatus> progress = new Progress<AsyncOperationStatus>(operationStatus => { logger.LogInformation( $"Copying file {file.Name} status: {operationStatus.Status}"); }); var asyncMonitor = new AsyncMonitor<DriveItem>(client, locationHeader.AbsoluteUri); var result = await asyncMonitor.PollForOperationCompletionAsync(progress, CancellationToken.None);
为什么会返回SharePoint REST API的资源?
MS Graph是统一的API入口,但底层处理文档库类操作(比如文件复制)时,会依赖SharePoint原生REST API。当异步复制完成后,MS Graph的监控端点会直接重定向到SharePoint REST的资源地址——这是因为最终的driveItem数据实际存储在SharePoint后端,直接返回原生地址能减少转发开销,提升效率。
但问题出在令牌受众不匹配:你当前使用的MS Graph令牌,受众(audience)是https://graph.microsoft.com,而SharePoint REST API要求令牌的受众是对应的SharePoint站点域名(比如https://{tenant-name}.sharepoint.com),所以用Graph令牌访问SharePoint REST地址就会触发Invalid Audience错误。
是否需要令牌交换?
是的,但更简便的方式是避开直接访问SharePoint REST地址,改用MS Graph端点获取最终的driveItem:
- 优先用Graph查询替代重定向地址:当监控到复制状态变为
Completed时,直接通过MS Graph的drives/{drive-id}/items/{item-id}端点查询目标文件,不需要处理重定向后的SharePoint地址。 - 令牌交换(备选方案):如果一定要用重定向地址,需要用当前Graph令牌的刷新令牌,向Azure AD请求受众为目标SharePoint站点的新令牌,再用新令牌访问该地址。但这种方式步骤繁琐,不如直接用Graph查询高效。
优化后的代码示例
修改逻辑,在复制完成时直接通过Graph API获取目标文件:
var copyResponse = await client.Sites[siteId].Lists[listId].Drive.Items[file.DriveItemId] .Copy(file.Name, reference) .Request() .PostResponseAsync(); var locationHeader = copyResponse.HttpHeaders.Location; if (locationHeader == null) { return null; } if (!locationHeader.IsAbsoluteUri) { locationHeader = new Uri(client.BaseUrl + locationHeader.OriginalString); } DriveItem result = null; IProgress<AsyncOperationStatus> progress = new Progress<AsyncOperationStatus>(async operationStatus => { logger.LogInformation($"Copying file {file.Name} status: {operationStatus.Status}"); // 复制完成时,直接通过Graph查询目标文件 if (operationStatus.Status == AsyncOperationStatusState.Completed) { // 从ResourceLocation中解析目标item的ID和driveID,转换为Graph端点格式 var resourceUri = new Uri(operationStatus.ResourceLocation); // 提取路径中的drives/{driveId}/items/{itemId}部分 var graphPath = resourceUri.PathAndQuery.Replace("/_api/v2.0/", "/v1.0/"); result = await client.HttpProvider.SendAsync(new HttpRequestMessage(HttpMethod.Get, $"https://graph.microsoft.com{graphPath}")) .ContinueWith(t => t.Result.Content.ReadAsAsync<DriveItem>().Result); } }); var asyncMonitor = new AsyncMonitor<DriveItem>(client, locationHeader.AbsoluteUri); await asyncMonitor.PollForOperationCompletionAsync(progress, CancellationToken.None); return result;
内容的提问来源于stack exchange,提问作者Kavrat

