Asp.net Core Web API调用Graph API操作Outlook日历报错求助
问题描述
已创建Asp.net Core Web API应用,用于通过Microsoft Graph API连接Outlook日历并添加新事件。已创建Microsoft Office 365 E3商业试用账户,并使用该账户在Azure门户的Active Directory中完成应用注册。
认证类代码如下:
using System.Net.Http; using System.Net.Http.Headers; using System.Security; using System.Threading.Tasks; using Microsoft.Identity.Client; using Microsoft.Graph; namespace Helpers { public class MsalAuthenticationProvider : IAuthenticationProvider { private static MsalAuthenticationProvider _singleton; private IPublicClientApplication _clientApplication; private string[] _scopes; private string _username; private SecureString _password; private string _userId; private MsalAuthenticationProvider(IPublicClientApplication clientApplication, string[] scopes, string username, SecureString password) { _clientApplication = clientApplication; _scopes = scopes; _username = username; _password = password; _userId = null; } public static MsalAuthenticationProvider GetInstance(IPublicClientApplication clientApplication, string[] scopes, string username, SecureString password) { if (_singleton == null) { _singleton = new MsalAuthenticationProvider(clientApplication, scopes, username, password); } return _singleton; } public async Task AuthenticateRequestAsync(HttpRequestMessage request) { var accessToken = await GetTokenAsync(); request.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken); } public async Task<string> GetTokenAsync() { if (!string.IsNullOrEmpty(_userId)) { try { var account = await _clientApplication.GetAccountAsync(_userId); if (account != null) { var silentResult = await _clientApplication.AcquireTokenSilent(_scopes, account).ExecuteAsync(); return silentResult.AccessToken; } } catch (MsalUiRequiredException) { } } var result = await _clientApplication.AcquireTokenByUsernamePassword(_scopes, _username, _password).ExecuteAsync(); _userId = result.Account.HomeAccountId.Identifier; return result.AccessToken; } } }
控制器代码如下:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Runtime.Versioning; using System.Security; using System.Threading.Tasks; using Helpers; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Graph; using Microsoft.Identity.Client; using WebAPI_OneDrive.Models; namespace WebAPI_OneDrive.Controllers { //[Route("api/[controller]")] [ApiController] public class OutlookController : ControllerBase { [HttpPost] [Route("Outlook/CreateEvent")] public async Task<string> CreateEventAsync(/*CredentialsModel model*/) { CredentialsModel model = new CredentialsModel(); model.applicationId = "9403804b-f9b6-4bb0-b8dd-2f40b4347ef1"; model.tenantId = "926349b9-42cb-46c0-bb34-970256de5c41"; model.userName = "alibaba@alibaba123456.onmicrosoft.com"; model.password = "p@ssw0rd"; var userName = model.userName; var stringPassword = model.password; var applicationId = model.applicationId; var tenantId = model.tenantId; var userPassword = new SecureString(); foreach (char c in stringPassword) { userPassword.AppendChar(c); } var config = LoadAppSettings(applicationId, tenantId); var graphClient = GetAuthenticatedGraphClient(config, userName, userPassword); //Create Event var @event = new Event { Subject = "Let's go for lunch", Body = new ItemBody { ContentType = BodyType.Html, Content = "Does noon work for you?" }, Start = new DateTimeTimeZone { DateTime = "2022-07-19T12:00:00", TimeZone = "Pacific Standard Time" }, End = new DateTimeTimeZone { DateTime = "2022-07-19T14:00:00", TimeZone = "Pacific Standard Time" }, Location = new Location { DisplayName = "Harry's Bar" }, Attendees = new List<Attendee>() { new Attendee { EmailAddress = new EmailAddress { Address = "samanthab@contoso.onmicrosoft.com", Name = "Samantha Booth" }, Type = AttendeeType.Required } }, AllowNewTimeProposals = true, TransactionId = "7E163156-7762-4BEB-A1C6-729EA81755A7" }; var Event = await graphClient.Me.Calendar.Events .Request() .AddAsync(@event); var EventId = Event.Id; return EventId; } private static IConfigurationRoot LoadAppSettings(string applicationId, string tenantId) { try { var config = new ConfigurationBuilder() .SetBasePath(System.IO.Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", false, true) .Build(); config["applicationId"] = applicationId; config["tenantId"] = tenantId; if (string.IsNullOrEmpty(config["applicationId"]) || string.IsNullOrEmpty(config["tenantId"])) { return null; } return config; } catch (System.IO.FileNotFoundException) { return null; } } private static IAuthenticationProvider CreateAuthorizationProvider(IConfigurationRoot config, string userName, SecureString userPassword) { var clientId = config["applicationId"]; var authority = $"https://login.microsoftonline.com/{config["tenantId"]}/v2.0"; List<string> scopes = new List<string>(); scopes.Add("User.Read"); scopes.Add("Calendars.Read"); scopes.Add("Calendars.Read.Shared"); scopes.Add("Calendars.ReadWrite"); scopes.Add("Calendars.Read.Shared"); //scopes.Add("Files.Read"); //scopes.Add("Files.ReadWrite"); //scopes.Add("Files.Read.All"); //scopes.Add("Files.ReadWrite.All"); //scopes.Add("Files.Read.Selected"); //scopes.Add("Files.ReadWrite.Selected"); //scopes.Add("Files.ReadWrite.AppFolder"); var cca = PublicClientApplicationBuilder.Create(clientId) .WithAuthority(authority) .Build(); return MsalAuthenticationProvider.GetInstance(cca, scopes.ToArray(), userName, userPassword); } private static GraphServiceClient GetAuthenticatedGraphClient(IConfigurationRoot config, string userName, SecureString userPassword) { var authenticationProvider = CreateAuthorizationProvider(config, userName, userPassword); var graphClient = new GraphServiceClient(authenticationProvider); return graphClient; } //Model public class CredentialsModel { ///credentials public string password { get; set; } public string userName { get; set; } public string applicationId { get; set; } public string tenantId { get; set; } } } }
调用Outlook/CreateEvent接口时出现错误,错误截图如下:

错误分析与修复方案
从错误截图的AADSTS7000218错误码可知,问题核心是用户名/密码认证流(ROPC)被禁用,具体修复步骤如下:
1. 开启应用的公共客户端流
当前代码使用PublicClientApplication,需确保Azure AD应用注册中允许公共客户端流:
- 登录Azure门户,进入目标应用注册页面
- 切换到「认证」标签,找到「高级设置」区域
- 将「允许公共客户端流」设置为「是」,保存更改
2. 检查租户安全默认值
若租户开启了安全默认值,ROPC流会被强制禁用:
- 进入Azure Active Directory的「属性」页面
- 查看「安全默认值」状态,若为开启状态,可选择关闭(需评估安全风险),或改用其他认证方式
3. 替换为更安全的认证方式
ROPC流需明文存储用户密码,安全性极低,不适合Web API场景,推荐以下两种替代方案:
(1)客户端凭证流(后台服务场景)
适用于无需用户交互的后台任务:
- 在应用注册中添加「应用权限」(如
Calendars.ReadWrite.All),并获取管理员同意 - 修改认证代码,使用
ConfidentialClientApplication获取令牌:
var cca = ConfidentialClientApplicationBuilder.Create(clientId) .WithClientSecret(clientSecret) .WithAuthority(authority) .Build(); var result = await cca.AcquireTokenForClient(scopes).ExecuteAsync();
- 调用Graph API时,使用
graphClient.Users["目标用户ID"].Calendar.Events替代graphClient.Me
(2)授权码流(用户交互场景)
适用于需要代表特定用户操作的场景:
- 在应用注册中添加重定向URI,开启授权码流
- 前端引导用户登录获取授权码,后端用授权码交换访问令牌
- 使用
AuthorizationCodeProvider处理认证逻辑
4. 代码优化点
- 移除硬编码凭证:将
applicationId、tenantId及用户凭证存入appsettings.json,避免敏感信息泄露 - 清理重复权限:代码中重复添加了
Calendars.Read.Shared,可删除重复项 - 取消单例认证提供者:
MsalAuthenticationProvider的单例模式会导致多用户场景下令牌冲突,建议为每个请求或用户实例化独立的认证提供者
内容的提问来源于stack exchange,提问作者shahzad aftab
相关产品推荐
相关产品推荐

