Поделиться через


Руководство: Аутентификация пользователей в настольном приложении WPF

Применимо: Белый круг с серым символом X. Арендаторы рабочей силы Зеленый круг с символом белой галочки. Внешние арендаторы (узнать больше)

В этом руководстве показано, как создать классическое приложение Формы презентации Windows (WPF) и подготовить его к проверке подлинности с помощью Центра администрирования Microsoft Entra.

Изучив это руководство, вы:

  • Настройте настольное приложение WPF для использования его данных регистрации приложения.
  • Создайте настольное приложение, выполняющее вход пользователя и получающее токен от имени пользователя.

Предпосылки

Создайте настольное приложение WPF

  1. Откройте терминал и перейдите в папку, в которой хотите жить проект.

  2. Инициализировать настольное приложение WPF и перейти к его корневой папке.

    dotnet new wpf --language "C#" --name sign-in-dotnet-wpf
    cd sign-in-dotnet-wpf
    

Установка пакетов

Установите поставщиков конфигурации, которые помогают нашему приложению считывать данные конфигурации из пар ключ-значение в файле настроек приложения. Эти абстракции конфигурации обеспечивают возможность привязки значений конфигурации к экземплярам объектов .NET.

dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.Json
dotnet add package Microsoft.Extensions.Configuration.Binder

Установите библиотеку аутентификации Microsoft (MSAL), содержащую все ключевые компоненты, необходимые для получения токена. Вы также установите библиотеку брокера MSAL, которая обрабатывает взаимодействие с брокерами проверки подлинности на рабочем столе.

dotnet add package Microsoft.Identity.Client
dotnet add package Microsoft.Identity.Client.Broker

Создание файла appsettings.json и добавление конфигураций регистрации

  1. Создайте файл appsettings.json в корневой папке приложения.

  2. Добавьте сведения о регистрации приложения в файл appsettings.json .

    {
        "AzureAd": {
            "Authority": "https://<Enter_the_Tenant_Subdomain_Here>.ciamlogin.com/",
            "ClientId": "<Enter_the_Application_Id_Here>"
        }
    }
    
    • Замените Enter_the_Tenant_Subdomain_Here поддомен каталога (клиента).
    • Замените Enter_the_Application_Id_Here идентификатором приложения (клиента), зарегистрированного ранее.
  3. После создания файла параметров приложения мы создадим другой файл с именем AzureAdConfig.cs , который поможет считывать конфигурации из файла параметров приложения. Создайте файл AzureAdConfig.cs в корневой папке приложения.

  4. В файле AzureAdConfig.js определите геттеры и сеттеры для свойств ClientId и Authority. Добавьте следующий код:

    namespace sign_in_dotnet_wpf
    {
        public class AzureAdConfig
        {
            public string Authority { get; set; }
            public string ClientId { get; set; }
        }
    }
    

Использование личного домена URL-адреса (необязательно)

Используйте настраиваемый домен для полного брендирования URL-адреса аутентификации. С точки зрения пользователя, во время проверки подлинности пользователи остаются на вашем домене, а не перенаправляются на доменное имя ciamlogin.com.

Выполните следующие действия, чтобы использовать личный домен:

  1. Выполните действия, описанные в разделе "Включение пользовательских доменов URL для приложений во внешних клиентах", чтобы включить пользовательский домен URL во внешнем клиенте.

  2. Откройте файл appsettings.json:

    1. Измените значение свойства Authority на https://Enter_the_Custom_Domain_Here/Enter_the_Tenant_ID_Here. Замените Enter_the_Custom_Domain_Here доменом личного URL-адреса и Enter_the_Tenant_ID_Here идентификатором клиента. Если у вас нет идентификатора клиента, узнайте, как прочитать сведения о клиенте.
    2. Добавьте свойство knownAuthorities со значением [Enter_the_Custom_Domain_Here].

После внесения изменений в файл appsettings.json, если ваш пользовательский домен URL — это login.contoso.com, а идентификатор арендатора — aaaabbbb-0000-cccc-1111-dddd2222eeee, то файл должен выглядеть примерно так, как показано в следующем фрагменте:

{
    "AzureAd": {
        "Authority": "https://login.contoso.com/aaaabbbb-0000-cccc-1111-dddd2222eeee",
        "ClientId": "Enter_the_Application_Id_Here",
        "KnownAuthorities": ["login.contoso.com"]
    }
}

Изменение файла проекта

  1. Перейдите к файлу sign-in-dotnet-wpf.csproj в корневой папке приложения.

  2. В этом файле сделайте следующее:

    1. Измените файл sign-in-dotnet-wpf.csproj , чтобы указать приложению копировать файл appsettings.json в выходной каталог при компиляции проекта. Добавьте следующий фрагмент кода в файл sign-in-dotnet-wpf.csproj :
    2. Настройте целевую платформу для сборки windows10.0.19041.0, чтобы упростить чтение кэшированного токена из кэша токенов, как вы увидите во вспомогательном классе кэша токенов.
    <Project Sdk="Microsoft.NET.Sdk">
    
        ...
    
        <!-- Set target framework to target windows10.0.19041.0 build -->
        <PropertyGroup>
            <OutputType>WinExe</OutputType>
            <TargetFramework>net7.0-windows10.0.19041.0</TargetFramework> <!-- target framework -->
            <RootNamespace>sign_in_dotnet_wpf</RootNamespace>
            <Nullable>enable</Nullable>
            <UseWPF>true</UseWPF>
        </PropertyGroup>
    
        <!-- Copy appsettings.json file to output folder. -->
        <ItemGroup>
            <None Remove="appsettings.json" />
        </ItemGroup>
    
        <ItemGroup>
            <EmbeddedResource Include="appsettings.json">
                <CopyToOutputDirectory>Always</CopyToOutputDirectory>
            </EmbeddedResource>
        </ItemGroup>
    </Project>
    

Создать вспомогательный класс кэша токенов

Создайте вспомогательный класс кэша маркеров, который инициализирует кэш маркеров. Приложение пытается считывать маркер из кэша, прежде чем пытаться получить новый маркер. Если маркер не найден в кэше, приложение получает новый маркер. После выхода кэш очищается от всех учетных записей и всех соответствующих маркеров доступа.

  1. Создайте файл TokenCacheHelper.cs в корневой папке приложения.

  2. Откройте файл TokenCacheHelper.cs. Добавьте пакеты и пространства имен в файл. В следующих шагах вы заполняете этот файл логикой кода, добавив соответствующую логику в TokenCacheHelper класс.

    using System.IO;
    using System.Security.Cryptography;
    using Microsoft.Identity.Client;
    
    namespace sign_in_dotnet_wpf
    {
        static class TokenCacheHelper{}
    }
    
  3. Добавьте конструктор в TokenCacheHelper класс, определяющий путь к файлу кэша. Для настольных приложений в виде пакетов (пакетов MSIX, также называемых настольным мостом) каталог выполнения сборки доступен только для чтения. В этом случае необходимо использовать Windows.Storage.ApplicationData.Current.LocalCacheFolder.Path + "\msalcache.bin", которая является папкой для чтения и записи, предназначенной для каждого приложения в упакованных приложениях.

    namespace sign_in_dotnet_wpf
    {
        static class TokenCacheHelper
        {
            static TokenCacheHelper()
            {
                try
                {
                    CacheFilePath = Path.Combine(Windows.Storage.ApplicationData.Current.LocalCacheFolder.Path, ".msalcache.bin3");
                }
                catch (System.InvalidOperationException)
                {
                    CacheFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location + ".msalcache.bin3";
                }
            }
            public static string CacheFilePath { get; private set; }
            private static readonly object FileLock = new object();
        }
    }
    
    
  4. Добавьте код для обработки сериализации кэша маркеров. Интерфейс ITokenCache реализует общедоступный доступ к операциям кэша. ITokenCache интерфейс содержит методы для подписки на события сериализации кэша, а интерфейс ITokenCacheSerializer предоставляет методы, которые необходимо использовать в событиях сериализации кэша, для сериализации и десериализации кэша. TokenCacheNotificationArgs содержит параметры, используемые вызовом MSALMicrosoft.Identity.Client, обращающимся к кэшу. ITokenCacheSerializer интерфейс доступен в TokenCacheNotificationArgs обратном вызове.

    Добавьте в класс TokenCacheHelper следующий код.

        static class TokenCacheHelper
        {
            static TokenCacheHelper()
            {...}
            public static string CacheFilePath { get; private set; }
            private static readonly object FileLock = new object();
    
            public static void BeforeAccessNotification(TokenCacheNotificationArgs args)
            {
                lock (FileLock)
                {
                    args.TokenCache.DeserializeMsalV3(File.Exists(CacheFilePath)
                            ? ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath),
                                                     null,
                                                     DataProtectionScope.CurrentUser)
                            : null);
                }
            }
    
            public static void AfterAccessNotification(TokenCacheNotificationArgs args)
            {
                if (args.HasStateChanged)
                {
                    lock (FileLock)
                    {
                        File.WriteAllBytes(CacheFilePath,
                                           ProtectedData.Protect(args.TokenCache.SerializeMsalV3(),
                                                                 null,
                                                                 DataProtectionScope.CurrentUser)
                                          );
                    }
                }
            }
        }
    
        internal static void EnableSerialization(ITokenCache tokenCache)
        {
            tokenCache.SetBeforeAccess(BeforeAccessNotification);
            tokenCache.SetAfterAccess(AfterAccessNotification);
        }
    

    В методе BeforeAccessNotification вы считываете кэш из файловой системы и если кэш не пуст, вы десериализируете его и загружаете его. Метод AfterAccessNotification вызывается после того, как Microsoft.Identity.Client MSAL обращается к кэшу. Если кэш изменился, сериализуете его и сохраните изменения в кэше.

    EnableSerialization содержит методы ITokenCache.SetBeforeAccess() и ITokenCache.SetAfterAccess().

    • ITokenCache.SetBeforeAccess() задает делегат, который уведомляется перед тем, как любой метод библиотеки получит доступ к кэшу. Это позволяет делегату десериализировать запись кэша для приложения и учетных записей, указанных в разделе TokenCacheNotificationArgs.
    • ITokenCache.SetAfterAccess() задает делегат для уведомления после доступа к кэшу любого метода библиотеки. Это позволяет делегату сериализовать запись кэша для приложения и учетных записей, указанных в разделе TokenCacheNotificationArgs.

Создайте пользовательский интерфейс настольного приложения WPF

Измените файл MainWindow.xaml , чтобы добавить элементы пользовательского интерфейса для приложения. Откройте файл MainWindow.xaml в корневой папке приложения и добавьте следующий фрагмент кода с разделом <Grid></Grid> элемента управления.

    <StackPanel Background="Azure">
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
            <Button x:Name="SignInButton" Content="Sign-In" HorizontalAlignment="Right" Padding="5" Click="SignInButton_Click" Margin="5" FontFamily="Segoe Ui"/>
            <Button x:Name="SignOutButton" Content="Sign-Out" HorizontalAlignment="Right" Padding="5" Click="SignOutButton_Click" Margin="5" Visibility="Collapsed" FontFamily="Segoe Ui"/>
        </StackPanel>
        <Label Content="Authentication Result" Margin="0,0,0,-5" FontFamily="Segoe Ui" />
        <TextBox x:Name="ResultText" TextWrapping="Wrap" MinHeight="120" Margin="5" FontFamily="Segoe Ui"/>
        <Label Content="Token Info" Margin="0,0,0,-5" FontFamily="Segoe Ui" />
        <TextBox x:Name="TokenInfoText" TextWrapping="Wrap" MinHeight="70" Margin="5" FontFamily="Segoe Ui"/>
    </StackPanel>

Этот код добавляет ключевые элементы пользовательского интерфейса. Методы и объекты, обрабатывающие функциональные возможности элементов пользовательского интерфейса, определяются в файле MainWindow.xaml.cs , который мы создадим на следующем шаге.

  • Кнопка для входа пользователя в систему. SignInButton_Click метод вызывается, когда пользователь выбирает эту кнопку.
  • Кнопка, которая выходит из системы пользователя. SignOutButton_Click метод вызывается, когда пользователь выбирает эту кнопку.
  • Текстовое поле, отображающее сведения о результатах проверки подлинности после попытки входа пользователя. Сведения, отображаемые здесь, возвращаются ResultText объектом.
  • Текстовое поле, отображающее информацию о токене после успешного входа пользователя. Сведения, отображаемые здесь, возвращаются TokenInfoText объектом.

Добавление кода в файл MainWindow.xaml.cs

Файл MainWindow.xaml.cs содержит код, который предоставляет логику среды выполнения для поведения элементов пользовательского интерфейса в файле MainWindow.xaml .

  1. Откройте файл MainWindow.xaml.cs в корневой папке приложения.

  2. Добавьте следующий код в файл для импорта пакетов и определите заполнители для создаваемых методов.

    using Microsoft.Identity.Client;
    using System;
    using System.Linq;
    using System.Windows;
    using System.Windows.Interop;
    
    namespace sign_in_dotnet_wpf
    {
        public partial class MainWindow : Window
        {
            string[] scopes = new string[] { };
    
            public MainWindow()
            {
                InitializeComponent();
            }
    
            private async void SignInButton_Click(object sender, RoutedEventArgs e){...}
    
            private async void SignOutButton_Click(object sender, RoutedEventArgs e){...}
    
            private void DisplayBasicTokenInfo(AuthenticationResult authResult){...}
        }
    }
    
  3. Добавьте следующий код в метод SignInButton_Click. Этот метод вызывается, когда пользователь нажимает кнопку входа .

    private async void SignInButton_Click(object sender, RoutedEventArgs e)
    {
        AuthenticationResult authResult = null;
        var app = App.PublicClientApp;
    
        ResultText.Text = string.Empty;
        TokenInfoText.Text = string.Empty;
    
        IAccount firstAccount;
    
        var accounts = await app.GetAccountsAsync();
        firstAccount = accounts.FirstOrDefault();
    
        try
        {
            authResult = await app.AcquireTokenSilent(scopes, firstAccount)
                    .ExecuteAsync();
        }
        catch (MsalUiRequiredException ex)
        {
            try
            {
                authResult = await app.AcquireTokenInteractive(scopes)
                    .WithAccount(firstAccount)
                    .WithParentActivityOrWindow(new WindowInteropHelper(this).Handle) 
                    .WithPrompt(Prompt.SelectAccount)
                    .ExecuteAsync();
            }
            catch (MsalException msalex)
            {
                ResultText.Text = $"Error Acquiring Token:{System.Environment.NewLine}{msalex}";
            }
            catch (Exception ex)
            {
                ResultText.Text = $"Error Acquiring Token Silently:{System.Environment.NewLine}{ex}";
                return;
            }
    
            if (authResult != null)
            {
                ResultText.Text = "Sign in was successful.";
                DisplayBasicTokenInfo(authResult);
                this.SignInButton.Visibility = Visibility.Collapsed;
                this.SignOutButton.Visibility = Visibility.Visible;
            }
        }
    }
    

    GetAccountsAsync() возвращает все доступные учетные записи в кэше маркеров пользователя для приложения. Интерфейс IAccount предоставляет сведения об одной учетной записи.

    Чтобы приобрести токены, приложение пытается тихо получить токен с помощью AcquireTokenSilent метода, чтобы проверить, находится ли допустимый токен в кэше. Метод AcquireTokenSilent может завершиться ошибкой, например, из-за того, что пользователь вышел. Когда MSAL обнаруживает, что проблему можно устранить, если потребуется интерактивное действие, она выдает MsalUiRequiredException исключение. Это исключение приводит к тому, что приложение получает маркер в интерактивном режиме.

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

  4. Добавьте следующий код в метод SignOutButton_Click. Этот метод вызывается, когда пользователь нажимает кнопку выхода .

    private async void SignOutButton_Click(object sender, RoutedEventArgs e)
    {
        var accounts = await App.PublicClientApp.GetAccountsAsync();
        if (accounts.Any())
        {
            try
            {
                await App.PublicClientApp.RemoveAsync(accounts.FirstOrDefault());
                this.ResultText.Text = "User has signed-out";
                this.TokenInfoText.Text = string.Empty;
                this.SignInButton.Visibility = Visibility.Visible;
                this.SignOutButton.Visibility = Visibility.Collapsed;
            }
            catch (MsalException ex)
            {
                ResultText.Text = $"Error signing-out user: {ex.Message}";
            }
        }
    }
    

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

  5. Добавьте следующий код в метод DisplayBasicTokenInfo. Этот метод отображает основные сведения о токене.

    private void DisplayBasicTokenInfo(AuthenticationResult authResult)
    {
        TokenInfoText.Text = "";
        if (authResult != null)
        {
            TokenInfoText.Text += $"Username: {authResult.Account.Username}" + Environment.NewLine;
            TokenInfoText.Text += $"{authResult.Account.HomeAccountId}" + Environment.NewLine;
        }
    }
    

Добавление кода в файл App.xaml.cs

App.xaml — это место, в котором вы объявляете ресурсы, используемые в приложении. Это точка входа для приложения. App.xaml.cs — это файл программной части для App.xaml. App.xaml.cs также определяет начальное окно приложения.

Откройте файл App.xaml.cs в корневой папке приложения, а затем добавьте в него следующий код.

using System.Windows;
using System.Reflection;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Broker;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;

namespace sign_in_dotnet_wpf
{
    public partial class App : Application
    {
        static App()
        {
            CreateApplication();
        }

        public static void CreateApplication()
        {
            var assembly = Assembly.GetExecutingAssembly();
            using var stream = assembly.GetManifestResourceStream("sign_in_dotnet_wpf.appsettings.json");
            AppConfiguration = new ConfigurationBuilder()
                .AddJsonStream(stream)
                .Build();

            AzureAdConfig azureADConfig = AppConfiguration.GetSection("AzureAd").Get<AzureAdConfig>();

            var builder = PublicClientApplicationBuilder.Create(azureADConfig.ClientId)
                .WithAuthority(azureADConfig.Authority)
                .WithDefaultRedirectUri();

            _clientApp = builder.Build();
            TokenCacheHelper.EnableSerialization(_clientApp.UserTokenCache);
        }

        private static IPublicClientApplication _clientApp;
        private static IConfiguration AppConfiguration;
        public static IPublicClientApplication PublicClientApp { get { return _clientApp; } }
    }
}

На этом шаге вы загружаете файл appsettings.json . Построитель конфигураций помогает считывать конфигурации приложения, определенные в файле appsettings.json . Приложение WPF также определяется как общедоступное клиентское приложение, поскольку это настольное приложение. Этот TokenCacheHelper.EnableSerialization метод включает сериализацию кэша токенов.

Запуск приложения

Запустите приложение и войдите, чтобы протестировать приложение.

  1. В терминале перейдите в корневую папку приложения WPF и запустите приложение, выполнив команду dotnet run в терминале.

  2. После запуска примера появится окно с кнопкой входа . Нажмите кнопку входа .

    Скриншот экрана входа в систему для настольного приложения WPF.

  3. На странице входа введите адрес электронной почты учетной записи. Если у вас нет учетной записи, выберите Нет учетной записи? Создайте учетную запись, что запустит процесс регистрации. Следуйте этой последовательности действий, чтобы создать новую учетную запись и войти.

  4. После входа вы увидите экран, отображающий успешно выполненный вход и основные сведения о учетной записи пользователя, содержащейся в полученном токене. Основная информация отображается в разделе "Информация о токене" на экране входа.

См. также