Почему мое приложение всегда вызывает Program.PublicClientApp.AcquireTokenAsync?

Это мой код для аутентификации для использования Microsoft Graph с Outlook:

public async Task AquireToken()
{
    try
    {
        if (_AuthResult == null)
        {
            _AuthResult = await Program.PublicClientApp.AcquireTokenSilentAsync(
                _scopes, Program.PublicClientApp.Users.FirstOrDefault());
        }
    }
    catch (MsalUiRequiredException ex)
    {
        // A MsalUiRequiredException happened on AcquireTokenSilentAsync. 
        // This indicates you need to call AcquireTokenAsync to acquire a token.
        System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");

        try
        {
            _AuthResult = await Program.PublicClientApp.AcquireTokenAsync(_scopes);
        }
        catch (MsalException msalex)
        {
            _ResultsText = $"Error Acquiring Token:{System.Environment.NewLine}{msalex}";
        }
    }
    catch (Exception ex)
    {
        _ResultsText = $"Error Acquiring Token Silently:{System.Environment.NewLine}{ex}";
    }

    if (_AuthResult != null)
    {
        _ResultsText = await GetHttpContentWithToken(_graphAPIEndpoint, _AuthResult.AccessToken);
    }
}

Он основан на образцы предоставлены Microsoft. В выводе консоли говорится:

Срок действия токена истекает: 09.04.2017 14:18:06 +01: 00

Этот код отображается из:

$"Token Expires: {_AuthResult.ExpiresOn.ToLocalTime()}" + Environment.NewLine;

Таким образом, это означает, что токен действителен в течение одного часа. Поэтому, если я снова запущу свою утилиту, я ожидаю, что она будет использовать тот же токен, пока не потребуется новый. Но это не так. Он всегда показывает подсказку.

Какой шаг я пропустил?

Исключение

Согласно запросу в комментариях, это детали исключения:

MsalUiRequiredException: в AcquiretokenSilent API был передан нулевой пользователь. Передайте объект пользователя или вызовите аутентификацию acquToken.

Это может помочь

Microsoft Graph SDK - вход

Мне нужно просмотреть предоставленный ответ:

вам необходимо реализовать кеш токенов и использовать AcquireTokenSilentAsync. https://docs.microsoft.com/en-us/outlook/rest/dotnet-tutorial содержит пример веб-приложения.


person Andrew Truckle    schedule 04.09.2017    source источник
comment
Каков вывод `System.Diagnostics.Debug.WriteLine ($ MsalUiRequiredException: {ex.Message});`? Исключение вызывает вызов, поэтому вам нужно выяснить, почему у него возникают проблемы с получением токена в автоматическом режиме.   -  person Ewald    schedule 04.09.2017
comment
@Ewald Кажется, это как-то связано с пользователем?   -  person Andrew Truckle    schedule 04.09.2017
comment
Нулевой пользователь обязательно вызовет сбой, что, по логике приложения, вызовет неожиданный вызов. Итак, ваша основная причина в том, что вы передаете нулевого пользователя. Если бы вы этого не сделали, я бы ожидал, что звонок сработает так, как вы думаете. Вам нужно выяснить, почему Program.PublicClientApp.Users.FirstOrDefault() дает вам нулевого пользователя.   -  person Ewald    schedule 04.09.2017
comment
@Ewald В учебнике, на который есть ссылка в соответствующем ответе, говорится о TokenCache, и, похоже, мне не хватает этого аспекта. Но это руководство предназначено для приложения ASP.NET, а не для приложения Console. Я изо всех сил пытаюсь понять, как перенести этот учебник в свой контекст. Кроме того, в руководстве создается кеш токенов в сеансе приложения, а не сохраняется его на жесткий диск между экземплярами (насколько я могу судить).   -  person Andrew Truckle    schedule 04.09.2017
comment
Логика должна быть примерно такой же, я думаю, консольному приложению нужно будет кэшировать пользовательские данные. Однако консольные приложения - это мрачное искусство - я не собираюсь сильно помогать, кроме как указывать вам во всевозможных неправильных направлениях!   -  person Ewald    schedule 04.09.2017


Ответы (2)


Я воспользовался реестром. Сохраните токен при успешном входе в систему, а затем перезванивайте токен каждый раз, когда вам нужно использовать GraphServiceClient. Если срок действия токена истек или появляется ошибка, вы можете восстановить процесс входа в систему и сохранить новый токен.

 public static async Task<GraphServiceClient> GetAuthenticatedClientAsync()
    {
        GraphServiceClient graphClient = new GraphServiceClient(
            new DelegateAuthenticationProvider(
                async (requestMessage) =>
                {
                    string appID = ConfigurationManager.AppSettings["ida:AppId"];

                    PublicClientApplication PublicClientApp = new PublicClientApplication(appID);
                    string[] _scopes = new string[] { "Calendars.read", "Calendars.readwrite", "Mail.read", "User.read" };

                    AuthenticationResult authResult = null;

                    string keyName = @"Software\xxx\Security";
                    string valueName = "Status";
                    string token = "";

                    RegistryKey regKey = Registry.CurrentUser.OpenSubKey(keyName, false);
                    if (regKey != null)
                    {
                        token = (string)regKey.GetValue(valueName);
                    }

                    if (regKey == null || string.IsNullOrEmpty(token))
                    {
                        authResult = await PublicClientApp.AcquireTokenAsync(_scopes); //Opens Microsoft Login Screen
                        //code if key Not Exist
                        RegistryKey key;
                        key = Registry.CurrentUser.CreateSubKey(@"Software\xxx\Security");
                        key.OpenSubKey(@"Software\xxx\Security", true);
                        key.SetValue("Status", authResult.AccessToken);
                        key.SetValue("Expire", authResult.ExpiresOn.ToString());
                        key.Close();
                        // Append the access token to the request.
                        requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
                    }
                    else
                    {
                        //code if key Exists
                        RegistryKey reg = Registry.CurrentUser.OpenSubKey(@"Software\xxx\Login", true);
                        // set value of "abc" to "efd"
                        token = (string)regKey.GetValue(valueName);
                        // Append the access token to the request.
                        requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
                    }
                }));
        try
        {      
            Microsoft.Graph.User me = await graphClient.Me.Request().GetAsync();

        }
        catch(Exception e)
        {
            if (e.ToString().Contains("Access token validation failure") || e.ToString().Contains("Access token has expired"))
            {
                string keyName = @"Software\xxx\Security";
                using (RegistryKey key = Registry.CurrentUser.OpenSubKey(keyName, true))
                {
                    if (key != null)
                    {
                        key.DeleteValue("Status");
                        key.DeleteValue("Expire");
                    }
                    else
                    {
                        MessageBox.Show("Error! Something went wrong. Please contact your administrator.", "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
                await GetAuthenticatedClientAsync();
            }
        }

        return graphClient;
    }
person William Botha    schedule 06.09.2017
comment
Спасибо. Я внес некоторые коррективы. Например, я сначала анализирую существующее значение срока действия, чтобы узнать, знаем ли мы, что нам нужно отобразить окно учетных данных. - person Andrew Truckle; 06.09.2017
comment
Рад, что смог помочь. Удачного кодирования. - person William Botha; 06.09.2017

MSAL .NET на рабочем столе не предлагает постоянного кеша, потому что нет очевидного хранилища, на которое он мог бы положиться из коробки (в то время как MSAL предлагает постоянное хранилище в UWP, Xamarin iOS и Android, где доступно изолированное хранилище приложений). По умолчанию MSAL .NET на рабочем столе использует кэш памяти, который исчезает, как только процесс завершается. См. https://azure.microsoft.com/en-us/resources/samples/active-directory-dotnet-desktop-msgraph-v2/ в качестве примера, демонстрирующего, как предоставить простой файловый кеш, который будет сохранять токены во время выполнения.

person vibronet    schedule 05.09.2017