c# - EWS 连接给出未经授权的 (401) 错误
问题描述
我一直在开发一个程序,该程序可以扫描交换收件箱以查找来自指定地址的特定电子邮件。目前,该程序会读取收件箱、下载附件并将电子邮件移至另一个文件夹。但是,从 EWS 服务器拉大约 15 次后,连接开始给出 401 Unauthorized 错误,直到我重新启动程序。该程序设置为通过 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 错误进行进一步跟踪后,导致令牌的 1 小时寿命结束时出现问题。这是因为原始 OAuth 令牌的初始生命周期为 1 小时。但是,可以通过设置代码在需要时自动刷新令牌来解决此问题。这是为遇到此问题的其他人解决此问题的代码。
身份验证管理器:
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,它将尝试查看是否存在用于获取令牌的帐户。接下来,它将尝试检索先前缓存的令牌,或者如果令牌在不到 5 分钟内过期,则刷新令牌。如果有可用的令牌,它将返回给主应用程序。如果没有可用的令牌,它将使用提供的用户名和密码获取新令牌。这段代码的实现看起来像这样,
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 服务时使用它来获取和更新新令牌所需的所有内容。这是我发现解决上述问题的解决方案。
推荐阅读
- matlab - Matlab如何通过BNC端口发送TTL信号?
- jenkins - Jenkins:如何在管道中运行 groovy 脚本
- typescript - 基于输入的 TypeScript 返回类型
- azure - Azure ARM 模板
- python - 寻找最相似的句子匹配
- php - 在 HTML 表 PHP 中显示的 SQL 数据
- python - 在使用 readlines() 创建的字符串中插入多行使用 for-loop
- mongodb - 使用spring boot的mongodb vs CRUD操作
- tfs-2015 - 如何在 TFS 2015 对象模型中使用 continuationtoken:GetBuildsAsync?
- uber-api - 优步按需加急