Partilhar via


Tutorial: Autenticar usuários em seu aplicativo de desktop WPF

Este tutorial é a parte final de uma série que demonstra a criação de um aplicativo de área de trabalho WPF (Windows Presentation Form) e prepará-lo para autenticação usando o centro de administração do Microsoft Entra. Na Parte 1 desta série, você registrou um aplicativo e configurou fluxos de usuário em seu locatário externo. Este tutorial demonstra como criar seu aplicativo de área de trabalho .NET WPF e entrar e sair de um usuário usando o Microsoft Entra External ID.

Neste tutorial, você:

  • Configure um aplicativo de desktop WPF para usar seus detalhes de registro do aplicativo.
  • Crie um aplicativo de área de trabalho que conecte um usuário e adquira um token em nome do usuário.

Pré-requisitos

Criar um aplicativo de desktop WPF

  1. Abra o terminal e navegue até a pasta onde você deseja que seu projeto viva.

  2. Inicialize um aplicativo de desktop WPF e navegue até sua pasta raiz.

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

Instalar pacotes

Instale provedores de configuração que ajudam nosso aplicativo a ler dados de configuração de pares chave-valor em nosso arquivo de configurações do aplicativo. Essas abstrações de configuração fornecem a capacidade de vincular valores de configuração a instâncias de objetos .NET.

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

Instale a Biblioteca de Autenticação da Microsoft (MSAL) que contém todos os principais componentes necessários para adquirir um token. Você também instala a biblioteca do agente MSAL que lida com interações com agentes de autenticação da área de trabalho.

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

Criar appsettings.json arquivo e adicionar configurações de registro

  1. Crie appsettings.json arquivo na pasta raiz do aplicativo.

  2. Adicione detalhes de registro do aplicativo ao arquivo appsettings.json .

    {
        "AzureAd": {
            "Authority": "https://<Enter_the_Tenant_Subdomain_Here>.ciamlogin.com/",
            "ClientId": "<Enter_the_Application_Id_Here>"
        }
    }
    
    • Substitua Enter_the_Tenant_Subdomain_Here pelo subdomínio Directory (locatário).
    • Substitua Enter_the_Application_Id_Here pelo ID do aplicativo (cliente) do aplicativo que você registrou anteriormente.
  3. Depois de criar o arquivo de configurações do aplicativo, criaremos outro arquivo chamado AzureAdConfig.cs que ajudará você a ler as configurações do arquivo de configurações do aplicativo. Crie o arquivo AzureAdConfig.cs na pasta raiz do aplicativo.

  4. No arquivo AzureAdConfig.js, defina os getters e setters para as ClientId propriedades e Authority . Adicione o seguinte código:

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

Usar domínio de URL personalizado (opcional)

Use um domínio personalizado para marcar totalmente a URL de autenticação. Do ponto de vista do usuário, os usuários permanecem no seu domínio durante o processo de autenticação, em vez de serem redirecionados para ciamlogin.com nome de domínio.

Siga estas etapas para usar um domínio personalizado:

  1. Use as etapas em Habilitar domínios de URL personalizados para aplicativos em locatários externos para habilitar o domínio de URL personalizado para seu locatário externo.

  2. Abra appsettings.json arquivo:

    1. Atualize o Authority valor da propriedade para https://Enter_the_Custom_Domain_Here/Enter_the_Tenant_ID_Here. Substitua Enter_the_Custom_Domain_Here pelo seu domínio de URL personalizado e Enter_the_Tenant_ID_Here pelo seu ID de inquilino. Se não tiver o ID do inquilino, saiba como ler os detalhes do inquilino.
    2. Adicionar knownAuthorities propriedade com um valor [Enter_the_Custom_Domain_Here].

Depois de fazer as alterações no arquivo appsettings.json, se o domínio de URL personalizado estiver login.contoso.com e o ID do locatário for aaaabbbb-0000-cccc-1111-dddd2222eeee, o arquivo deverá ser semelhante ao seguinte trecho:

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

Modificar o arquivo de projeto

  1. Navegue até o arquivo sign-in-dotnet-wpf.csproj na pasta raiz do aplicativo.

  2. Nesse arquivo, execute as duas etapas a seguir:

    1. Modifique o arquivo sign-in-dotnet-wpf.csproj para instruir seu aplicativo a copiar o arquivo appsettings.json para o diretório de saída quando o projeto for compilado. Adicione a seguinte parte do código ao arquivo sign-in-dotnet-wpf.csproj :
    2. Defina a estrutura de destino para a compilação windows10.0.19041.0 de destino para ajudar com a leitura do token armazenado em cache do cache de token, como você verá na classe auxiliar de cache de token.
    <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>
    

Criar uma classe auxiliar de cache de token

Crie uma classe auxiliar de cache de token que inicializa um cache de token. O aplicativo tenta ler o token do cache antes de tentar adquirir um novo token. Se o token não for encontrado no cache, o aplicativo adquirirá um novo token. Ao sair, o cache é limpo de todas as contas e todos os tokens de acesso correspondentes.

  1. Crie um arquivo TokenCacheHelper.cs na pasta raiz do aplicativo.

  2. Abra o arquivo TokenCacheHelper.cs . Adicione os pacotes e namespaces ao arquivo. Nas etapas a seguir, você preenche esse arquivo com a lógica de código adicionando a lógica relevante à TokenCacheHelper classe.

    using System.IO;
    using System.Security.Cryptography;
    using Microsoft.Identity.Client;
    
    namespace sign_in_dotnet_wpf
    {
        static class TokenCacheHelper{}
    }
    
  3. Adicione o TokenCacheHelper construtor à classe que define o caminho do arquivo de cache. Para aplicativos de área de trabalho empacotados (pacotes MSIX, também chamados de ponte da área de trabalho), a pasta assembly em execução é somente leitura. Nesse caso, precisamos usar Windows.Storage.ApplicationData.Current.LocalCacheFolder.Path + "\msalcache.bin" uma pasta de leitura/gravação por aplicativo para aplicativos empacotados.

    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. Adicione código para manipular a serialização do cache de token. A ITokenCache interface implementa o acesso público às operações de cache. ITokenCache interface contém os métodos para assinar os eventos de serialização de cache, enquanto a interface ITokenCacheSerializer expõe os métodos que você precisa usar nos eventos de serialização de cache, a fim de serializar/desserializar o cache. TokenCacheNotificationArgs contém parâmetros usados pelaMicrosoft.Identity.Client chamada (MSAL) que acessa o cache. ITokenCacheSerializer A interface está disponível em retorno de TokenCacheNotificationArgs chamada.

    Adicione o seguinte código à TokenCacheHelper classe:

        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 No método, você lê o cache do sistema de arquivos e, se o cache não estiver vazio, desserializa-o e carrega-o. O AfterAccessNotification método é chamado depois que Microsoft.Identity.Client (MSAL) acessa o cache. Se o cache tiver sido alterado, serialize-o e persista as alterações no cache.

    O EnableSerialization contém o ITokenCache.SetBeforeAccess() e ITokenCache.SetAfterAccess() métodos:

    • ITokenCache.SetBeforeAccess() Define um delegado a ser notificado antes que qualquer método de biblioteca acesse o cache. Isso dá uma opção ao delegado para desserializar uma entrada de cache para o aplicativo e as contas especificadas no TokenCacheNotificationArgs.
    • ITokenCache.SetAfterAccess() Define um delegado a ser notificado depois que qualquer método de biblioteca acessa o cache. Isso dá uma opção ao delegado para serializar uma entrada de cache para o aplicativo e as contas especificadas no TokenCacheNotificationArgs.

Criar a interface do usuário do aplicativo de desktop WPF

Modifique o arquivo MainWindow.xaml para adicionar os elementos da interface do usuário para o aplicativo. Abra o arquivo MainWindow.xaml na pasta raiz do aplicativo e adicione a seguinte parte do código com a <Grid></Grid> seção de controle.

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

Esse código adiciona elementos-chave da interface do usuário. Os métodos e objetos que manipulam a funcionalidade dos elementos da interface do usuário são definidos no arquivo de MainWindow.xaml.cs que criamos na próxima etapa.

  • Um botão que inicia sessão no utilizador. SignInButton_Click método é chamado quando o usuário seleciona esse botão.
  • Um botão que desconecta o usuário. SignOutButton_Click método é chamado quando o usuário seleciona esse botão.
  • Uma caixa de texto que exibe os detalhes do resultado da autenticação depois que o usuário tenta entrar. As informações exibidas aqui são retornadas ResultText pelo objeto.
  • Uma caixa de texto que exibe os detalhes do token depois que o usuário entra com êxito. As informações exibidas aqui são retornadas TokenInfoText pelo objeto.

Adicionar código ao arquivo MainWindow.xaml.cs

O arquivo MainWindow.xaml.cs contém o código que fornece a lógica de tempo de execução para o comportamento dos elementos da interface do usuário no arquivo MainWindow.xaml .

  1. Abra o arquivo MainWindow.xaml.cs na pasta raiz do aplicativo.

  2. Adicione o seguinte código no arquivo para importar os pacotes e definir espaços reservados para os métodos que criamos.

    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. Adicione o seguinte código ao método SignInButton_Click. Esse método é chamado quando o usuário seleciona o botão de entrada .

    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() Retorna todas as contas disponíveis no cache de token de usuário para o aplicativo. A IAccount interface representa informações sobre uma única conta.

    Para adquirir tokens, o aplicativo tenta adquirir o token silenciosamente usando o AcquireTokenSilent método para verificar se um token aceitável está no cache. O AcquireTokenSilent método pode falhar, por exemplo, porque o usuário saiu. Quando o MSAL deteta que o problema pode ser resolvido exigindo uma ação interativa, ele lança uma MsalUiRequiredException exceção. Essa exceção faz com que o aplicativo adquira um token interativamente.

    Chamar o AcquireTokenInteractive método resulta em uma janela que solicita que os usuários entrem. Normalmente, as aplicações exigem que os utilizadores iniciem sessão interativamente na primeira vez que precisam de se autenticar. Eles também podem precisar entrar quando uma operação silenciosa para adquirir um token. Depois AcquireTokenInteractive é executado pela primeira vez, AcquireTokenSilent torna-se o método usual a ser usado para obter tokens

  4. Adicione o seguinte código ao método SignOutButton_Click. Esse método é chamado quando o usuário seleciona o botão Sair .

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

    O SignOutButton_Click método limpa o cache de todas as contas e todos os tokens de acesso correspondentes. Da próxima vez que o utilizador tentar iniciar sessão, terá de o fazer de forma interativa.

  5. Adicione o seguinte código ao método DisplayBasicTokenInfo. Esse método exibe informações básicas sobre o token.

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

Adicionar código ao arquivo App.xaml.cs

App.xaml é onde você declara recursos que são usados em todo o aplicativo. É o ponto de entrada para o seu aplicativo. App.xaml.cs é o arquivo code-behind para App.xaml. App.xaml.cs também define a janela Iniciar para seu aplicativo.

Abra o arquivo App.xaml.cs na pasta raiz do aplicativo e adicione o seguinte código a ele.

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

Nesta etapa, você carrega o arquivo appsettings.json . O construtor de configurações ajuda você a ler as configurações do aplicativo definidas no arquivo appsettings.json . Você também define o aplicativo WPF como um aplicativo cliente público, pois é um aplicativo de área de trabalho. O TokenCacheHelper.EnableSerialization método habilita a serialização do cache de token.

Executar a aplicação

Execute seu aplicativo e entre para testar o aplicativo

  1. No seu terminal, navegue até a pasta raiz do seu aplicativo WPF e execute o aplicativo executando o comando dotnet run no seu terminal.

  2. Depois de iniciar o exemplo, você verá uma janela com um botão de entrada . Selecione o botão Entrar .

    Captura de ecrã do ecrã de início de sessão para uma aplicação de ambiente de trabalho WPF.

  3. Na página de início de sessão, introduza o endereço de e-mail da sua conta. Se você não tiver uma conta, selecione Sem conta? Crie um, que inicia o fluxo de inscrição. Siga esse fluxo para criar uma nova conta e entrar.

  4. Depois de entrar, você verá uma tela exibindo o login bem-sucedido e informações básicas sobre sua conta de usuário armazenadas no token recuperado. As informações básicas são exibidas na seção Informações do token da tela de entrada

Consulte também