Я пытаюсь просто загрузить файл из 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
}
расшифруйте токен доступа, который вы получили в jwt.io
, и проверьте, содержит ли он утверждение roles
, которое имеет правильное разрешение API.
@user2250152 user2250152 Я использую разрешения приложения.
@TinyWang Спасибо, этот сайт потрясающий. Маркер содержит роли «Sites.ReadWrite.All» и «Files.ReadWrite.All».
Я согласен как с @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();
Это выглядит очень многообещающе! Я пробую это сейчас, и я думаю, что это правильное направление. Я дам вам знать, когда у меня будет больше.
@AJL Я не знаю, как получить идентификатор диска. На вопрос, есть ли у вас URL файла string fileUrl = $"https://{tenant}.sharepoint.com/sites/{siteName}/{library}/{fileName}";
Также нет кода, как скачать файл. @Sridevi Не могли бы вы добавить код, как загрузить файл?
Мне потребовалось много времени, чтобы найти правильный идентификатор сайта, но как только я это сделал, ответ, предоставленный @Sridevi, отлично сработал для получения файла. Вот только бы понять, что с этим делать! спасибо
Вы добавили делегированные разрешения или разрешения приложений на портал Azure? На основе вашего кода вам нужны разрешения приложения.