Соединение EWS Выдает Несанкционированную ошибку (401)

#c# #.net #exchangewebservices

Вопрос:

Я работал над программой, которая сканирует почтовый ящик exchange на наличие определенных писем с указанного адреса. В настоящее время программа считывает входящие, загружает вложение и перемещает электронное письмо в другую папку. Однако примерно после 15 запросов с сервера EWS соединение начинает выдавать 401 несанкционированную ошибку, пока я не перезапущу программу. Программа настроена для входа в систему через OAuth, так как базовая аутентификация отключена системным администратором. Ниже приведен код, который я использую для получения подключения к exchange и чтения электронных писем из папки «Входящие».

Код подключения к Exchange:

 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().Resu<

        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;
        }
    }
 

Ошибка возникает, когда отправитель электронной почты пытается либо создать соединение exchange, либо запросить электронные письма из папки. В любом случае код выдаст ошибку и выдаст мне 401 несанкционированный при использовании учетных данных, которые работают в течение первой дюжины раз, а затем завершаются неудачей после стольких попыток. Я попробовал это с несколькими разными учетными записями, и проблема сохраняется со всеми из них, и я убедился, что приложение авторизовано для доступа к почтовому ящику exchange. Любые предложения или помощь будут высоко оценены.

Комментарии:

1. Проверьте, не истек ли тайм-аут. Попробуйте один поиск, а затем подождите 15 минут и попробуйте второй.

2. @jdweng В настоящее время тайм-аут установлен на 5 минут, и его ошибка произойдет раньше

3. Тогда проблема в таймауте, а не в количестве пакетов? Я пытаюсь определить, является ли сбой таймаутом количества отправляемых сообщений. Ваша публикация подразумевает, что сбой произошел из-за того, что было отправлено около 15 сообщений. Я думаю, что это тайм-аут.

4. @jdweng выяснил, что срок действия токена истекает через первый час. На данный момент я получаю ошибку invalid_lifetime и должен выяснить, как обновить токен.

Ответ №1:

После дальнейшего отслеживания ошибки 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 resu<
    }
}
 

Я использую прямую аутентификацию по имени пользователя и паролю, но строка кода также может быть переключена на аутентификацию пользователя с помощью интерактивных методов. Код по существу создает новый экземпляр диспетчера аутентификации с приложением Publicclient, используемым для его инициализации, в котором содержатся идентификатор приложения и идентификатор арендатора. После инициализации вы можете вызвать функцию 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;
     }
}
 

Приведенный выше код содержит все, что необходимо для создания нового диспетчера аутентификации и использования его для получения и обновления новых токенов при использовании служб EWS через OAuth. Это решение, которое я нашел, чтобы устранить проблему, описанную выше.