c# - EWS 连接出现未经授权 (401) 错误

标签 c# .net exchangewebservices

我一直在开发一个程序,该程序可以扫描交换收件箱以查找来自指定地址的特定电子邮件。目前,该程序读取收件箱,下载附件,并将电子邮件移动到另一个文件夹。但是,在从 EWS 服务器拉取大约 15 次之后,连接开始给出 401 未经授权的错误,直到我重新启动程序。该程序设置为通过 OAuth 登录,因为系统管理员禁用了基本身份验证。下面是我用来获取交换连接并从收件箱读取电子邮件的代码。

交换连接代码:

public static async Task<ExchangeService> GetExchangeConnection()
    {
        var pcaOptions = new PublicClientApplicationOptions
        {
            ClientId = AppID,
            TenantId = TenantID,
        };

        var pca = PublicClientApplicationBuilder.CreateWithApplicationOptions(pcaOptions).Build();

        var ewsScopes = new string[] { "https://outlook.office365.com/EWS.AccessAsUser.All" };

        var securePassword = new SecureString();
        foreach (char c in Pasword)
            securePassword.AppendChar(c);

        try
        {
            var authResult = await pca.AcquireTokenByUsernamePassword(ewsScopes, Username, securePassword).ExecuteAsync();

            ExchangeService exchangeService = new ExchangeService()
            {
                Credentials = new OAuthCredentials(authResult.AccessToken),
                Url = new Uri("https://outlook.office365.com/ews/exchange.asmx"),
            };

            return exchangeService;
        }
        catch
        {
            return null;
        }
    }

电子邮件检索器

public static List<Email> RetreiveEmails()
    {
        ExchangeService exchangeConnection = GetExchangeConnection().Result;

        try
        {
            List<Email> Emails = new List<Email>();
            TimeSpan ts = new TimeSpan(0, -5, 0, 0);
            DateTime date = DateTime.Now.Add(ts);
            SearchFilter.IsGreaterThanOrEqualTo EmailTimeFilter = new SearchFilter.IsGreaterThanOrEqualTo(ItemSchema.DateTimeReceived, date);

            if (exchangeConnection != null)
            {
                FindItemsResults<Item> findResults = exchangeConnection.FindItems(WellKnownFolderName.Inbox, EmailTimeFilter, new ItemView(10));

                foreach (Item item in findResults)
                {
                    if (item.Subject != null)
                    {
                        EmailMessage message = EmailMessage.Bind(exchangeConnection, item.Id);
                        message.Load(new PropertySet(BasePropertySet.FirstClassProperties, ItemSchema.TextBody));
                        Emails.Add(new Email(message.DateTimeReceived, message.From.Name.ToString(), message.Subject, message.TextBody.ToString(), (message.HasAttachments) ? "Yes" : "No", message.Id.ToString()));
                    }
                }
            }

            exchangeConnection = null;
            return Emails;
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
            return null;
        }
    }

当电子邮件检索程序尝试创建交换连接或从文件夹请求电子邮件时,会发生此错误。无论哪种情况,代码都会出错,并在使用前十几次有效但在多次尝试后失败的凭据时给我 401 未经授权。我已经尝试使用多个不同的帐户,但问题仍然存在,并且我已确保应用程序有权访问交换收件箱。非常感谢任何建议或帮助。

最佳答案

在对 401 错误进行进一步跟踪后,导致 token 出现 1 小时生命周期结束的问题。这是因为原始 OAuth token 的初始生命周期为 1 小时。不过,可以通过设置代码在需要时自动刷新 token 来解决这个问题。以下是解决此问题的代码,供遇到此问题的其他人使用。

身份验证管理器:

class AuthenticationManager
{ 
    protected IPublicClientApplication App { get; set; }

    public AuthenticationManager(IPublicClientApplication app)
    {
        App = app;
    }

    public async Task<AuthenticationResult> AcquireATokenFromCacheOrUsernamePasswordAsync(IEnumerable<String> scopes, string username, SecureString password)
    {
        AuthenticationResult result = null;
        var accounts = await App.GetAccountsAsync();

        if (accounts.Any())
        {
            try
            {
                result = await (App as PublicClientApplication).AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync();
            }
            catch (MsalUiRequiredException)
            { }
        }

        if (result == null)
        {
            result = await (App as PublicClientApplication).AcquireTokenByUsernamePassword(scopes, username, password).ExecuteAsync();
        }

        return result;
    }
}

我使用直接用户名和密码身份验证,但代码行也可以切换为通过交互式方法获取用户身份验证。该代码本质上创建了一个身份验证管理器的新实例,其中包含用于初始化它的 PublicClientApplication,其中包含 appID 和tenantID。初始化后,您可以调用 AquireATokenFromCacheOrUsernamePasswordAsync ,它将尝试查看是否存在可获取 token 的帐户。接下来,它将尝试检索之前缓存的 token ,或者如果 token 在 5 分钟内过期,则刷新该 token 。如果有可用的 token ,它会将其返回到主应用程序。如果没有可用的 token ,它将使用提供的用户名和密码获取新 token 。这段代码的实现看起来像这样,

class ExchangeServices
{
     AuthenticationManager Manager = null;

     public ExchangeServices(String AppId, String TenantID)
     {
          var pcaOptions = new PublicClientApplicationOptions
          {
               ClientId = AppID,
               TenantId = TenantID,
          };
          var pca = PublicClientApplicationBuilder.CreateWithApplicationOptions(pcaOptions).Build();
          Manager = new AuthenticationManager(pca);
     }

     public static async Task<ExchangeService> GetExchangeService()
     {
          var ewsScopes = new string[] { "https://outlook.office365.com/EWS.AccessAsUser.All" }
          var securePassword = new SecureString();
          foreach(char c in Password)
               securePassword.AppendChar(c);

          var authResult = await Manager.AquireATokenFromCacheOrUsernamePasswordAsync(ewsScopes,           Username, securePassword);

          ExchangeService exchangeService = new ExchangeService()
          {
               Credentials = new OAuthCredentials(authResult.AccessToken),
               Url = new Uri("https://outlook.office365.com/ews/exchange.asmx");
          };
          return exchangeService;
     }
}

上面的代码是创建新的身份验证管理器所需的所有内容,并在通过 OAuth 使用 EWS 服务时使用它来获取和更新新 token 。这是我找到的解决上述问题的解决方案。

关于c# - EWS 连接出现未经授权 (401) 错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67112711/

相关文章:

c# - Magento 从 C# 应用程序通过 SOAP 添加产品

c# - .NET 终结器是否有超时?

.net - 在 WP8 上,我应该为 MultiBinding 类引用哪个程序集

web-services - Grails:不同格式的RESTful Web服务数据处理

c# - 重新使用 EWS 连接并为 Exchange 协商身份验证

outlook - 如何使用 EWS 从 Outlook 联系人读取扩展属性

c# - 如何更新DataTable中的所有自增列?

c# - LINQ 合并一个集合

c# - Unity3D调用外部dll

c# - 序列化包含 List<T> 的 List<T>