You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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接口时出现错误,错误截图如下:
错误截图1
错误截图2

错误分析与修复方案

从错误截图的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 09:33:15