С#: загрузка файла Sharepoint через MS Graph возвращает несанкционированный доступ

Я пытаюсь просто загрузить файл из Sharepoint, используя токен носителя Microsoft Graph. Я продолжаю получать ответ 401 Unauthorized. Приложение будет работать из 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;
            }
        }
    }
}

Приведенный выше код был собран из множества руководств. Он успешно захватывает токен и возвращает его основному методу, но затем я получаю ответ 401 Unauthorized.

Я добавил множество разрешений для приложения Azure, на которое нацелены ClientId и ClientSecret, включая Microsoft_Graph/Files.ReadWrite.All и Microsoft_Graph/Sites.ReadWrite.All.

Кто-нибудь, у кого есть опыт работы с Sharepoint и Graph, скажите мне, что происходит не так? Или если код можно просто как-то оптимизировать? Большое спасибо.

P.S. Вот пример всего ответа, который я получаю, если это поможет:

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
}

Вы добавили делегированные разрешения или разрешения приложений на портал Azure? На основе вашего кода вам нужны разрешения приложения.

user2250152 22.02.2023 08:43

Расшифруйте токен доступа, который вы получили в jwt.io, и проверьте, содержит ли он утверждение roles, которое имеет правильное разрешение API.

Tiny Wang 22.02.2023 09:11

@user2250152 user2250152 Я использую разрешения приложения.

AJ L 22.02.2023 15:24

@TinyWang Спасибо, этот сайт потрясающий. Маркер содержит роли «Sites.ReadWrite.All» и «Files.ReadWrite.All».

AJ L 22.02.2023 15:25
Запуск PHP на IIS без использования программы установки веб-платформы
Запуск PHP на IIS без использования программы установки веб-платформы
Установщик веб-платформы, предлагаемый компанией Microsoft, перестанет работать 31 декабря 2022 года. Его закрытие привело к тому, что мы не можем...
Оптимизация React Context шаг за шагом в 4 примерах
Оптимизация React Context шаг за шагом в 4 примерах
При использовании компонентов React в сочетании с Context вы можете оптимизировать рендеринг, обернув ваш компонент React в React.memo сразу после...
Библиотека для работы с мороженым
Библиотека для работы с мороженым
Лично я попрощался с операторами print() в python. Без шуток.
Настройка шаблона Metronic с помощью Webpack и Gulp
Настройка шаблона Metronic с помощью Webpack и Gulp
Я пишу эту статью, чтобы поделиться тем, как настроить макет Metronic с помощью Sass, поскольку Metronic предоставляет так много документации, и они...
Уроки CSS 6
Уроки CSS 6
Здравствуйте дорогие читатели, я Ферди Сефа Дюзгюн, сегодня мы продолжим с вами уроки css. Сегодня мы снова продолжим с так называемых классов.
Что такое Css? Для чего он используется?
Что такое Css? Для чего он используется?
CSS, или "Каскадные таблицы стилей", - это язык стилей, используемый в веб-страницах. CSS является одним из основных инструментов веб-разработки...
0
4
77
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий
Я согласен как с @TinyWang, так и с @user2250152, вам нужно добавить разрешения API типа Application при использовании клиента поток учетных данных.

Но когда я запустил ваш код после добавления разрешений Application, он все равно выдал мне несанкционированную ошибку.

Я попытался воспроизвести то же самое в своей среде и получил следующие результаты:

У меня есть одна библиотека документов SharePoint с именем TestDocLib, имеющая файлы, как показано ниже:

Я зарегистрировал одно приложение Azure AD и добавил такие же разрешения API типа Application, как показано ниже:

Когда я запустил тот же код С#, что и вы, я тоже получил токен доступа с ошибкой Unauthorized, как показано ниже:

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;
            }
        }
    }
}

Ответ:

Когда я расшифровал приведенный выше токен доступа, он имеет roles требование с обоими разрешениями, которые подтверждают, что токен действителен, как показано ниже:

Чтобы узнать, в чем именно проблема, попробуйте запустить тот же запрос в Postman и проверьте, получили ли вы такие же результаты или нет.

В моем случае я выполнил другой запрос графа в Postman, включив указанный выше токен доступа, и получил ссылку для загрузки файла в ответ, например:

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

Ответ:

Когда я скопировал приведенную выше ссылку для скачивания и запустил ее в браузере, файл успешно скачался, как показано ниже:

Чтобы подтвердить это, я проверил то же самое в проводнике и нашел файл sri12.docx в папке «Загрузки», как показано ниже:

Чтобы запустить тот же запрос в С#, вы можете использовать следующий код:

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();

Это выглядит очень многообещающе! Я пробую это сейчас, и я думаю, что это правильное направление. Я дам вам знать, когда у меня будет больше.

AJ L 22.02.2023 15:53

@AJL Я не знаю, как получить идентификатор диска. На вопрос, есть ли у вас URL файла string fileUrl = $"https://{tenant}.sharepoint.com/sites/{siteName}/{library}‌​/{fileName}"; Также нет кода, как скачать файл. @Sridevi Не могли бы вы добавить код, как загрузить файл?

user2250152 22.02.2023 15:56

Мне потребовалось много времени, чтобы найти правильный идентификатор сайта, но как только я это сделал, ответ, предоставленный @Sridevi, отлично сработал для получения файла. Вот только бы понять, что с этим делать! спасибо

AJ L 22.02.2023 22:08

Другие вопросы по теме