如何通过无人值守C#应用访问启用MFA的SharePoint 365 Excel文件
问题背景
我们拥有一个启用了MFA的SharePoint 365站点,该站点的某个项目文件夹中有一份Excel文件,希望通过无人值守C#应用读取并处理它。交互式操作时可完成MFA验证后打开文件,但尝试以下5种方案均抛出异常:
文件URL:
string excelUrl = @"https://company.sharepoint.com/teams/Projects/DocCtrl/Project.xlsm";
所有案例中WebClient统一请求头:
webClient.Headers[HttpRequestHeader.UserAgent] = "Other"; webClient.Headers["X-FORMS_BASED_AUTH_ACCEPTED"] = "f";
案例1
using (WebClient webClient = new WebClient()) { webClient.UseDefaultCredentials = true; using (MemoryStream memoryStream = new MemoryStream(webClient.DownloadData(excelUrl))) using (SpreadsheetDocument xlDocument = SpreadsheetDocument.Open(memoryStream, false)) readAndProcess(xlDocument); }
异常信息:webClient.DownloadData(excelUrl)处抛出:远程服务器返回错误: (401) 未授权。
案例2
(注:此方案需硬编码或存储密码,希望避免)
using (WebClient webClient = new WebClient()) { webClient.Credentials = new NetworkCredential(@"emailId@company.com", @"Password", "Domain"); using (MemoryStream memoryStream = new MemoryStream(webClient.DownloadData(excelUrl))) using (SpreadsheetDocument xlDocument = SpreadsheetDocument.Open(memoryStream, false)) readAndProcess(xlDocument); }
异常信息:webClient.DownloadData(excelUrl)处抛出:远程服务器返回错误: (401) 未授权。
案例3
(注:此方案需硬编码或存储密码,希望避免)
using (WebClient webClient = new WebClient()) { SecureString securePwd = new SecureString(); foreach (char chr in @"Password") securePwd.AppendChar(chr); webClient.Credentials = new SharePointOnlineCredentials(@"emailId@company.com", securePwd); using (MemoryStream memoryStream = new MemoryStream(webClient.DownloadData(excelUrl))) using (SpreadsheetDocument xlDocument = SpreadsheetDocument.Open(memoryStream, false)) readAndProcess(xlDocument); }
异常信息:webClient.DownloadData(excelUrl)处抛出:请求已中止: 请求已取消。合作伙伴返回无效登录名或密码错误。更多信息,请参阅联合身份验证错误处理场景。
案例4
(注:此方案会弹出MFA验证窗口,不符合无人值守需求,希望避免)
using (WebClient webClient = new WebClient()) { var authManager = new OfficeDevPnP.Core.AuthenticationManager(); ClientContext ctx = authManager.GetWebLoginClientContext(@"https://company.sharepoint.com"); ctx.Load(ctx.Web, w => w.Title); ctx.ExecuteQuery(); using (MemoryStream memoryStream = new MemoryStream(webClient.DownloadData(excelUrl))) using (SpreadsheetDocument xlDocument = SpreadsheetDocument.Open(memoryStream, false)) readAndProcess(xlDocument); }
异常信息:webClient.DownloadData(excelUrl)处抛出:远程服务器返回错误: (401) 未授权。
案例5
(注:此方案仍会弹出MFA验证窗口,不符合无人值守需求,希望避免)
using (WebClient webClient = new WebClient()) { var authManager = new OfficeDevPnP.Core.AuthenticationManager(); ClientContext ctx = authManager.GetWebLoginClientContext(@"https://company.sharepoint.com"); SecureString securePwd = new SecureString(); foreach (char chr in @"Password") securePwd.AppendChar(chr); ctx.Credentials = new SharePointOnlineCredentials(@"emailId@company.com", securePwd); ctx.Load(ctx.Web, w => w.Title); ctx.ExecuteQuery(); using (MemoryStream memoryStream = new MemoryStream(webClient.DownloadData(excelUrl))) using (SpreadsheetDocument xlDocument = SpreadsheetDocument.Open(memoryStream, false)) readAndProcess(xlDocument); }
异常信息:ctx.ExecuteQuery()处抛出:合作伙伴返回无效登录名或密码错误。更多信息,请参阅联合身份验证错误处理场景。
解决方案
关于应用密码
应用密码仅适用于传统MFA(非现代身份验证)场景,当前大多数SharePoint Online租户已启用现代身份验证,应用密码无法绕过MFA验证,因此不能解决无人值守访问的问题。
推荐方案:使用Azure AD应用程序权限(客户端凭据流)
这是无人值守访问启用MFA的SharePoint Online的标准方案,步骤如下:
在Azure AD中注册应用程序
- 登录Azure门户,进入Azure Active Directory → 应用注册 → 新建注册。
- 设置应用名称,选择"账户仅限于此组织目录中的账户",无需重定向URI。
- 注册完成后,记录客户端ID和租户ID。
创建客户端密码
- 在应用注册的"证书和密码"选项卡,新建客户端密码,设置有效期,记录生成的密码值(仅显示一次)。
授予应用程序权限
- 进入应用注册的"API权限"选项卡,点击"添加权限" → 选择"SharePoint" → "应用权限"。
- 添加
Sites.Read.All(根据需求选择更精细的权限,如Sites.Selected),然后点击"授予管理员同意"。
C#代码实现
方式1:使用Microsoft Graph SDK下载文件
using Azure.Identity; using Microsoft.Graph; using System.IO; var clientId = "你的客户端ID"; var tenantId = "你的租户ID"; var clientSecret = "你的客户端密码"; var excelFilePath = "/teams/Projects/DocCtrl/Project.xlsm"; // SharePoint站点相对路径 var credential = new ClientSecretCredential(tenantId, clientId, clientSecret); var graphClient = new GraphServiceClient(credential); // 获取文件内容 var stream = await graphClient.Sites["company.sharepoint.com,teams/Projects,{site-id}"] .Drive .Root .ItemWithPath(excelFilePath) .Content .Request() .GetAsync(); // 读取并处理Excel文件 using (var memoryStream = new MemoryStream()) { await stream.CopyToAsync(memoryStream); memoryStream.Position = 0; using (var xlDocument = SpreadsheetDocument.Open(memoryStream, false)) { readAndProcess(xlDocument); } }
方式2:使用SharePoint CSOM + Azure AD身份验证
using Microsoft.SharePoint.Client; using System.Security; using Azure.Identity; using System.Net.Http.Headers; var siteUrl = "https://company.sharepoint.com/teams/Projects"; var clientId = "你的客户端ID"; var tenantId = "你的租户ID"; var clientSecret = "你的客户端密码"; // 获取访问令牌 var credential = new ClientSecretCredential(tenantId, clientId, clientSecret); var token = await credential.GetTokenAsync(new Azure.Core.TokenRequestContext(new[] { "https://sharepoint.com/.default" })); // 使用CSOM访问SharePoint using (var ctx = new ClientContext(siteUrl)) { ctx.ExecutingWebRequest += (s, e) => { e.WebRequestExecutor.RequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Token); }; // 获取文件 var file = ctx.Web.GetFileByServerRelativeUrl("/teams/Projects/DocCtrl/Project.xlsm"); ctx.Load(file); ctx.ExecuteQuery(); // 下载文件内容 var fileInfo = Microsoft.SharePoint.Client.File.OpenBinaryDirect(ctx, file.ServerRelativeUrl); using (var memoryStream = new MemoryStream()) { fileInfo.Stream.CopyTo(memoryStream); memoryStream.Position = 0; using (var xlDocument = SpreadsheetDocument.Open(memoryStream, false)) { readAndProcess(xlDocument); } } }
关键说明
- 客户端凭据流无需用户交互,完全适合无人值守应用。
- 权限需遵循最小权限原则,避免过度授权。
- 客户端密码需妥善存储,建议使用Azure Key Vault等安全存储方案,不要硬编码到代码中。
内容的提问来源于stack exchange,提问作者YogiWatcher

