c# - 如何使用应用级身份验证/访问令牌访问 Sharepoint 文件?
问题描述
我正在尝试访问 Sharepoint 文件而无需用户登录。
我可以通过方法 1 获取访问令牌:
var client = new RestClient("https://login.microsoftonline.com/app's-tenant-id-here/oauth2/token");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddHeader("Cookie", "fpc=AjMRWuGtzbFJgrxV0V1kMCkUHKO3AQAAAEqqRtgOAAAA");
request.AddParameter("resource", "https://graph.microsoft.com");
request.AddParameter("grant_type", "client_credentials");
request.AddParameter("client_id", "client-id-here");
request.AddParameter("client_secret", ".client-secret-here");
IRestResponse response = client.Execute(request);
或方法 2 - (这个给出了以下错误:'AppForSharePointOnlineWebToolkit.TokenHelper' 的类型初始化程序引发了异常。)
string siteUrl = "https://the-site-I-am-trying-to-access.sharepoint.com/sites/xxx/";
string realm = TokenHelper.GetRealmFromTargetUrl(new Uri(siteUrl));
string accessToken2 = TokenHelper.GetAppOnlyAccessToken(TokenHelper.SharePointPrincipal, new Uri(siteUrl).Authority, realm).AccessToken;
using (ClientContext cc = TokenHelper.GetClientContextWithAccessToken(siteUrl, accessToken2))
{
cc.Load(cc.Web, p => p.Title);
cc.ExecuteQuery();
Console.WriteLine(cc.Web.Title);
}
甚至方法3
HttpWebRequest endpointRequest = (HttpWebRequest)WebRequest.Create("https://the-site-I-am-trying-to-access.sharepoint.com/sites/xxx/_api/web/getfilebyserverrelativeurl('~/Shared%20Documents/picture.png')");
endpointRequest.Method = "GET";
endpointRequest.Accept = "application/json;odata=verbose";
endpointRequest.Headers.Add("Authorization", "Bearer " + accessToken);
HttpWebResponse endpointResponse = (HttpWebResponse)endpointRequest.GetResponse();
其中没有一个成功访问 Sharepoint。
所以我的问题是,我做错了什么还是有其他方法可以做到这一点?
解决方案
好吧,首先我建议尽可能使用 Graph API。至少它是查询数据的首选方式,它使事情变得更容易。
要通过 Graph API 访问 Microsoft 365 世界中的数据,需要在azure 门户> Azure Active Directory > 应用注册中创建新的应用注册。
有关更多信息,请参阅此链接:MS Docs 应用程序注册
创建新应用程序后,配置访问 SharePoint 数据所需的范围和权限(例如Sites.ReadWrite.All
,完全访问权限)。
之后,只需使用生成和提供的 clientID、clientSecret、tenantID 和范围来请求新的访问令牌。
我创建了一个类来为我执行所有 http 请求:
public class GraphClient
{
private const string LOGIN_URL = "https://login.microsoftonline.com/{0}/oauth2/v2.0/token";
private const string BASE_URL = "https://graph.microsoft.com";
protected internal string HttpBaseAddress { get; }
protected internal readonly HttpClient HttpClient;
public GraphClient(string tenantId, string clientId, string clientSecret, string version = "1.0")
{
var msgHandler = new GraphAuthMessageHandler(string.Format(LOGIN_URL, tenantId),
$"{BASE_URL}/.default",
clientId,
clientSecret,
new HttpClientHandler());
HttpBaseAddress = $"{BASE_URL}/v{version}/";
HttpClient = new HttpClient(msgHandler)
{
BaseAddress = new Uri(HttpBaseAddress),
Timeout = new TimeSpan(0, 2, 0)
};
HttpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
HttpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");
HttpClient.DefaultRequestHeaders.Add("Prefer", "odata.include-annotations=\"*\"");
HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
}
提供的 MessageHangler 请求令牌并将提供的访问令牌添加到标头:
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Threading;
using System.Threading.Tasks;
public class GraphAuthMessageHandler : DelegatingHandler
{
private readonly string _loginUrl;
private readonly string _clientId;
private readonly string _clientSecret;
private readonly string _scope;
public GraphAuthMessageHandler(string loginUrl, string scope, string clientId, string clientSecret, HttpMessageHandler innerHandler)
: base(innerHandler)
{
_loginUrl = loginUrl;
_clientId = clientId;
_clientSecret = clientSecret;
_scope = scope;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var result = await AcquireAccessToken();
request.Headers.Authorization = new AuthenticationHeaderValue(result.TokenType, result.AccessToken);
return await base.SendAsync(request, cancellationToken);
}
private async Task<AuthResponse> AcquireAccessToken()
{
var httpClient = new HttpClient();
var values = new List<KeyValuePair<string, string>>
{
new("client_id", _clientId),
new("client_secret", _clientSecret),
new("scope", _scope),
new("grant_type", "client_credentials")
};
var response = await httpClient.PostAsync(_loginUrl, new FormUrlEncodedContent(values));
return await response.Content.ReadFromJsonAsync<AuthResponse>();
}
}
编辑这是 AuthResponse 类,它只是将 json 响应映射到 C# 对象:
using System.Text.Json.Serialization;
public class AuthResponse
{
[JsonPropertyName("token_type")] public string TokenType { get; set; }
[JsonPropertyName("expires_in")] public int ExpiresIn { get; set; }
[JsonPropertyName("access_token")] public string AccessToken { get; set; }
}
然后简单地使用它:
var driveId = "your-drive-id-here";
var itemId = "your-item-id-here";
var client = new GraphClient(TENANT_ID, CLIENT_ID, CLIENT_SECRET);
var response = await client.HttpClient.GetAsync($"drives/{driveId}/items/{itemId}/content");
if (response.IsSuccessStatusCode)
{
var fileStream = await response.Content.ReadAsStreamAsync();
// do something with stream
}
// handle errors here
Microsoft Docs是让它工作的良好开端,它对我使用 Graph API 和 Graph Explorer 来测试对端点的查询和请求有很大帮助。
PS这只是一个简单的例子,肯定有改进的余地,但这有望为您指明正确的方向;)