【问题标题】:Refresh access token when talking to Exchange Online via EWS Managed API通过 EWS 托管 API 与 Exchange Online 交谈时刷新访问令牌
【发布时间】:2017-12-22 11:00:39
【问题描述】:

我正在编写一个应用程序以使用 EWS 托管 API 与 Exchange Online 通信,并使用 ADAL 库通过 OAuth 2.0 对我的应用程序进行身份验证。

访问令牌将在 60 分钟后过期。之后我需要刷新访问令牌。目前,我正在 StreamSubscriptionConnection OnNotificationEvent 处理程序以及我的 OnDisconnect 事件处理程序中执行此操作,以使用以下代码刷新 OAuth 访问令牌。

private void OnNotificationEventHandler(object sender, NotificationEventArgs args)
{
    exchangeService.Credentials = new OAuthCredentials(GetOAuthAccessToken().Result);

    // Do my work
}

我还在我的 OnDisconnect 事件处理程序中添加了相同的刷新访问令牌代码,因为 StreamSubscriptionConnection 最多只能保持打开 30 分钟。

private void OnDisconnectEventHandler(object sender, SubscriptionErrorEventArgs args)
{
    exchangeService.Credentials = new OAuthCredentials(GetOAuthAccessToken().Result);
    streamingSubscriptionConnection.Open();
}

这是我的访问令牌代码。

private async Task<string> GetOAuthAccessToken(PromptBehavior promptBehavior = PromptBehavior.Auto)
{
    var authenticationContext = new AuthenticationContext(myAadTenant);

    var authenticationResult = await authenticationContext.AcquireTokenAsync(exchangeOnlineServerName, myClientId, redirectUri, new PlatformParameters(promptBehavior));

    return authenticationResult.AccessToken;
}

即使认为上述方法“有效”,我觉得这不是处理这种情况的最佳方法,因为我几乎需要确保在与 EWS 通信时刷新我的访问令牌。如果我添加了另一个事件处理程序并且我忘记在事件处理程序中添加令牌刷新逻辑,如果我的访问令牌过期并且我需要在事件处理程序中调用 EWS,我可能会在处理该事件时得到 401。

上面的代码被简化了,我可以在与 EWS 通信时使用 try catch,如果我得到 401,我会刷新我的访问令牌并重试,但这并不能解决我上面提到的不便。

我认为应该有一种更简单的方法来处理问题,但我没有找到正确的文档。这是我在开发时参考的材料。 https://blogs.msdn.microsoft.com/webdav_101/2015/05/11/best-practices-ews-authentication-and-access-issues/

【问题讨论】:

  • 为什么不使用计时器线程每 n 分钟刷新一次令牌,并让它与所有经过身份验证的 Web 请求共享某种锁定,从而避免竞争条件?如果 EWS 延长每个请求的超时时间,您甚至可以在每个成功的 Web 请求时重置计时器。
  • 我不认为有一个计时器线程每 n 分钟刷新一次令牌可以解决这个问题。令牌每 60 分钟过期一次,我的应用程序需要响应新邮件事件。因此,如果下一个计时器在 65 分钟标记处刷新令牌,但在 62 分钟标记处收到事件,那么我仍然会遇到问题。为了处理这个问题,我仍然需要在响应事件并调用 EWS 之前刷新令牌。我想知道是否可以将我的 EWS 实例设置为在需要时自动刷新令牌。
  • 那么不要将计时器设置为每 65 分钟刷新一次,哈哈。如果刷新周期是 60 分钟,我会每 50 分钟进行一次。天哪。
  • 那行不通,因为在您已经拥有的 OAuth 访问令牌过期之前,您不会获得新的 OAuth 访问令牌。因此,在 55 分钟标记上调用 AcquireTokenAsync() 不会刷新令牌。当然,除非有一种我不知道的强制更新的方法。
  • 方法 #2 是简单地重新获取令牌以响应错误的请求。

标签: c# oauth-2.0 exchangewebservices adal ews-managed-api


【解决方案1】:

另一种方式是,当您通过 EWS 托管 API 与 Exchange 在线通信时,您需要提供 exchangeService 对象。并且您需要为每个请求捕获 401 异常,并且在遇到此异常后,您需要为 exchangeService 对象重新设置 Credentials 属性或重新创建此对象。

【讨论】:

  • 现在我没有捕获 401,而是刷新了“凭据”,这或多或少是相同的。即使我尝试捕获,我也需要为我对“exchangeService”的每个调用执行此操作。我试图找出是否有更优雅的解决方案,这样我就可以在一个地方而不是整个代码中使用 try-catch 逻辑。如果我想做 try-catch 401 或我现在正在做的事情,我需要在调用“exchangeService”之前把它放在任何地方,因为它们中的任何一个都可能由于令牌过期而返回 401。我还需要对我所有的事件处理程序做同样的事情。
  • 要全局处理异常,我们可以使用AppDomain.UnhandledException(参考here)。
  • 我们确实需要一个更好的令牌刷新解决方案,而不是尝试全局捕获异常并处理它们。为什么exchangeService 本身不能处理这个?全局捕获充其量是笨拙的,并且在许多情况下都不起作用(例如同时打开多个 EWS 连接)
【解决方案2】:

我同意应该有更好的方式来处理令牌刷新。如果 EWS API 本身可以管理令牌刷新,那就太好了,但作为一种解决方法,我所做的是......

将对 EWS 服务的引用放入公共/内部属性中)。然后我们需要确保该属性是 EWS 服务的单一访问点。

总的来说,这看起来像

public class Mailbox 
{
    private ExchangeService exchangeService;

    public ExchangeService ExchangeService
    {
        get
        {
            if (this.exchangeService == null)
            {
                // Initialise the service
                CreateExchangeService();
            }
            else
            {
                // Ensure token is still valid
                ValidateAuthentication();
            }

            return this.exchangeService;
        }
    }
}

我不打算详细说明 CreateExchangeService,但 ValidateAuthentication 是对 EWS 的基本调用,如果未经身份验证将引发异常

private void ValidateAuthentication()
{
    try
    {
        Folder inbox = Folder.Bind(this.exchangeService, WellKnownFolderName.Inbox);
    }
    catch //(WebException webEx) when (((HttpWebResponse)webEx.Response).StatusCode == HttpStatusCode.Unauthorized)
    {
        RefreshOAuthCredentials();
    }
}

RefreshOAuthCredentials 只会刷新令牌。同样,我在 StreamingSubscription 上也有一个 OnDisconnect 事件处理程序,它将尝试重新打开连接(如果重新打开失败则重新验证)。为简洁起见,此处均未显示。

主要的一点是,我们现在有一个对 EWS 服务的引用,该服务在每次调用之前执行身份验证检查和重新验证(如果需要)。例如绑定到邮件消息看起来像..

var mailMessage = EmailMessage.Bind(this.mailbox.ExchangeService, itemId);

毫无疑问,这会增加流量/延迟,如果有更好的方法可以避免这种情况。如果有人有更好的方法 - 我全神贯注!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-11-24
    • 2016-07-28
    • 2013-10-24
    • 1970-01-01
    • 1970-01-01
    • 2019-01-13
    • 1970-01-01
    相关资源
    最近更新 更多