C#:通过 MS Graph 下载 Sharepoint 文件返回未经授权

标签 c# azure sharepoint azure-functions microsoft-graph-api

我尝试使用 Microsoft Graph 不记名 token 从 Sharepoint 下载文件。我继续收到 401 未经授权的响应。该应用程序将从 Azure 内部运行,但在我弄清楚时当前正在本地运行。代码如下,用注释代替潜在的敏感信息:

using System.Net.Http.Headers;
using System.Text;

namespace SharePointFileDownload
{
    class Program
    {
        static async Task Main(string[] args)
        {
            try
            {
                string tenantId = /*My company's tenantId, from Azure*/;
                string accessToken = await GetGraphBearerToken();

                string tenant = /*the name of the tenant on sharepoint*/;
                string siteName = /*sharepoint site name*/;
                string library = /*sharepoint library name*/;
                string fileName = /*filename of the test document*/;

                string fileUrl = $"https://{tenant}.sharepoint.com/sites/{siteName}/{library}/{fileName}";

                using (var client = new HttpClient())
                {
                    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

                    string graphRequestUrl = $"https://graph.microsoft.com/v1.0/tenants/{tenantId}/drives/root:{fileUrl.Substring(fileUrl.IndexOf("/sites"))}:/content";

                    HttpResponseMessage response = await client.GetAsync(graphRequestUrl);

                    if (response.IsSuccessStatusCode)
                    {
                        byte[] fileContent = await response.Content.ReadAsByteArrayAsync();
                        File.WriteAllBytes("file.docx", fileContent);
                        Console.WriteLine("File downloaded successfully.");
                    }
                    else
                    {
                        Console.WriteLine("Failed to download file. Error: " + response.ReasonPhrase);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }

        public static async Task<string> GetGraphBearerToken()
        {
            try
            {
                string clientId = /*Azure ClientId for the registered application*/; 
                string clientSecret = /*Azure ClientSecret for the registered application*/;
                string tenantId = /*My company's tenantId, same as in the main method*/;

                var client = new HttpClient();
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(
                    new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));

                var requestBody = new StringContent($"grant_type=client_credentials&client_id={clientId}&client_secret={clientSecret}&scope=https://graph.microsoft.com/.default", Encoding.UTF8, "application/x-www-form-urlencoded");

                var response = await client.PostAsync($"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token", requestBody);

                if (response.IsSuccessStatusCode)
                {
                    var responseContent = await response.Content.ReadAsStringAsync();
                    // parse the responseContent to get the bearer token
                    Console.WriteLine("Bearer token: " + responseContent);
                    return responseContent;
                }
                else
                {
                    Console.WriteLine("Error fetching bearer token: " + response.StatusCode);
                    return String.Empty;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                return String.Empty;
            }
        }
    }
}

上面的代码是根据各种教程组装而成的。它成功获取 token 并将其返回到 main 方法,但随后我收到 401 Unauthorized 响应。

我已向 ClientId 和 ClientSecret 所针对的 Azure 应用程序添加了许多权限,包括 Microsoft_Graph/Files.ReadWrite.All 和 Microsoft_Graph/Sites.ReadWrite.All。

任何有 Sharepoint 和 Graph 经验的人都可以告诉我哪里出了问题吗?或者是否可以以任何方式优化代码?非常感谢。

附注这是我收到的整个回复的示例,如果有帮助的话:

StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
  Transfer-Encoding: chunked
  Strict-Transport-Security: max-age=31536000
  request-id: /*An ID, the same as client-request-id*/
  client-request-id: /*An ID, the same as request-id*/
  x-ms-ags-diagnostic: {"ServerInfo":{"DataCenter":"South Central US","Slice":"E","Ring":"5","ScaleUnit":"004","RoleInstance":/*Not sure if this is private, but here is a comment anyway*/}}
  WWW-Authenticate: Bearer realm="", authorization_uri="https://login.microsoftonline.com/common/oauth2/authorize", client_id=/*A ClientId that does not match the clientId variable I passed*/
  Date: Tue, 21 Feb 2023 16:55:47 GMT
  Content-Type: application/json
}

最佳答案

I agree with both @TinyWang and @user2250152, you need to add API permissions of Application type while using client credentials flow.

但是,当我在添加应用程序权限后运行您的代码时,它仍然给出未经授权错误。

我尝试在我的环境中重现相同的结果并得到以下结果:

我有一个名为 TestDocLib 的 SharePoint 文档库,其中包含如下文件:

enter image description here

我注册了一个 Azure AD 应用程序,并添加了与应用程序类型相同的 API 权限,如下所示:

enter image description here

当我运行与您相同的c#代码时,我也获得了访问 token ,并出现未经授权错误,如下所示:

using System.Net.Http.Headers;
using System.Text;

namespace SharePointFileDownload
{
    class Program
    {
        static async Task Main(string[] args)
        {
            try
            {
                string tenantId = <tenantID>;
                string accessToken = await GetGraphBearerToken();

                string tenant = <tenantname>;
                string siteName =<sitename>;
                string library = <documentlibrary name>;
                string fileName = <filename>;

                string fileUrl = $"https://{tenant}.sharepoint.com/sites/{siteName}/{library}/{fileName}";

                using (var client = new HttpClient())
                {
                    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

                    string graphRequestUrl = $"https://graph.microsoft.com/v1.0/tenants/{tenantId}/drives/root:{fileUrl.Substring(fileUrl.IndexOf("/sites"))}:/content";

                    HttpResponseMessage response = await client.GetAsync(graphRequestUrl);

                    if (response.IsSuccessStatusCode)
                    {
                        byte[] fileContent = await response.Content.ReadAsByteArrayAsync();
                        File.WriteAllBytes("file.docx", fileContent);
                        Console.WriteLine("File downloaded successfully.");
                    }
                    else
                    {
                        Console.WriteLine("Failed to download file. Error: " + response.ReasonPhrase);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }

        public static async Task<string> GetGraphBearerToken()
        {
            try
            {
                string clientId = <appID>; 
                string clientSecret = <secret>;
                string tenantId = <tenantID>;

                var client = new HttpClient();
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(
                    new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));

                var requestBody = new StringContent($"grant_type=client_credentials&client_id={clientId}&client_secret={clientSecret}&scope=https://graph.microsoft.com/.default", Encoding.UTF8, "application/x-www-form-urlencoded");

                var response = await client.PostAsync($"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token", requestBody);

                if (response.IsSuccessStatusCode)
                {
                    var responseContent = await response.Content.ReadAsStringAsync();
                    // parse the responseContent to get the bearer token
                    Console.WriteLine("Bearer token: " + responseContent);
                    return responseContent;
                }
                else
                {
                    Console.WriteLine("Error fetching bearer token: " + response.StatusCode);
                    return String.Empty;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                return String.Empty;
            }
        }
    }
}

回应:

enter image description here

当我解码上述访问 token 时,它具有 roles 声明,并具有确认 token 有效的两种权限,如下所示:

enter image description here

要了解问题到底出在哪里,请尝试在 Postman 中运行相同的查询,并检查是否得到相同的结果。

就我而言,我通过包含上述访问 token 在 Postman 中运行了不同的图形查询,并在响应中获取了文件下载链接,如下所示:

GET https://graph.microsoft.com/v1.0/sites/<siteID>/drives/<doclib driveID>/root/children/<filename>

回应:

enter image description here

当我复制上面的下载链接并在浏览器中运行它时,文件下载成功,如下所示:

enter image description here

为了确认这一点,我在文件资源管理器中检查了相同内容,并在下载文件夹中找到了 sri12.docx 文件,如下所示:

enter image description here

要在 C# 中运行相同的查询,您可以使用以下代码:

using Microsoft.Graph;
using Azure.Identity;

string tenantId = "<tenantID>";
string clientId = "<appID>";
string clientSecret = "<secret>";

// using Azure.Identity;
var options = new TokenCredentialOptions
{
    AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};

var scopes = new[] { "https://graph.microsoft.com/.default" };
var clientSecretCredential = new ClientSecretCredential(
    tenantId, clientId, clientSecret, options);

var graphClient = new GraphServiceClient(clientSecretCredential, scopes);

var driveItem = await graphClient.Sites["<siteID>"].Drives["<driveID>"].Root.Children["filename.docx"]
    .Request()
    .GetAsync();

关于C#:通过 MS Graph 下载 Sharepoint 文件返回未经授权,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75523670/

相关文章:

sql - 如何在 IDE 中将 Synapse Analytics SQL 脚本(JSON 格式)读取为 SQL 脚本?

azure - 在 Azure 中删除资源组是否会取消分配并删除该资源组内的所有内容?

sharepoint - SharePoint 中的默认版本控制

c# - 了解 WPF 派生 WIndow 类

c# - Slickgrid 为单元格/列或行添加颜色

c# - 如何在 Autofac 中运行时定义嵌套对象的值

azure - 在 MacOS 上使用 sourcetree 连接到 Azure devops 存储库

c# - 未返回 Active Directory 组

sharepoint - 加载此程序集将产生与其他实例不同的授权集。 (HRESULT : 0x80131401) 的异常

javascript - 是否可以将删除/放置请求发送到 Azure AD 安全 api 或使用 aadHttpClientFactory 以字符串形式获取 jwt token