Partilhar via


Criar uma aplicação de início de sessão do Windows Hello

Esta é a primeira parte de um passo a passo completo sobre como criar um aplicativo do Windows empacotado que usa o Windows Hello como uma alternativa aos sistemas tradicionais de autenticação de nome de usuário e senha. Nesse caso, o aplicativo é um aplicativo WinUI, mas a mesma abordagem pode ser usada com qualquer aplicativo do Windows empacotado, incluindo aplicativos WPF e Windows Forms. O aplicativo usa um nome de usuário para entrar e cria uma chave Hello para cada conta. Estas contas serão protegidas pelo PIN que está configurado nas Definições do Windows na configuração do Windows Hello.

Este passo a passo é dividido em duas partes: criando o aplicativo e conectando o serviço de back-end. Após terminar este artigo, siga para Parte 2: serviço de login do Windows Hello.

Antes de começar, leia a visão geral do Windows Hello para obter uma compreensão geral de como o Windows Hello funciona.

Comece

Para criar este projeto, você precisará de alguma experiência com C# e XAML. Você também precisará usar o Visual Studio 2022 em uma máquina Windows 10 ou Windows 11. Consulte Começar a desenvolver aplicativos do Windows para obter instruções completas sobre como configurar seu ambiente de desenvolvimento.

  • No Visual Studio, selecione Arquivo>Novo>Projeto.
  • Nos filtros pendentes da caixa de diálogo Novo Projeto, selecione C#/C++, Windows e WinUI, respectivamente.
  • Escolha Aplicação em Branco, Empacotada (WinUI 3 na Área de Trabalho) e dê o nome à sua aplicação de "WindowsHelloLogin".
  • Compilar e executar o novo aplicativo (F5), você deve ver uma janela em branco mostrada na tela. Feche o aplicativo.

Uma captura de ecrã da nova aplicação de Início de Sessão do Windows Hello em execução pela primeira vez

Exercício 1: Iniciar sessão com o Windows Hello

Neste exercício, você aprenderá como verificar se o Windows Hello está configurado no computador e como entrar em uma conta usando o Windows Hello.

  • No novo projeto crie uma nova pasta na solução chamada "Views". Esta pasta conterá as páginas para as quais serão navegadas neste exemplo. Clique com o botão direito do mouse no projeto no Gerenciador de Soluções, selecione Adicionar>Nova Pasta e renomeie a pasta para Exibições.

    Uma captura de tela da adição de uma nova pasta chamada Modos de Exibição ao projeto de Login do Windows Hello

  • Abra MainWindow.xaml e substitua o conteúdo de Window por um controle vazio StackPanel ou Grid. Implementaremos a navegação de página e navegaremos para uma nova página quando a MainWindow for carregada, para que não precisemos de nenhum conteúdo na MainWindow.

  • Adicione uma Title propriedade a MainWindow no XAML. O atributo deve ter esta aparência: Title="Windows Hello Login".

  • Remova o manipulador de eventos myButton_Click do MainWindow.xaml.cs para evitar erros de compilação. Este manipulador de eventos não é necessário para este exemplo.

  • Clique com o botão direito do mouse na nova pasta Exibições , selecione Adicionar>Novo Item e selecione o modelo Página em Branco . Nomeie esta página como "MainPage.xaml".

    Uma captura de tela da adição de uma nova página em branco ao projeto de login do Windows Hello

  • Abra o arquivo App.xaml.cs e atualize o manipulador OnLaunched para implementar a navegação de página para o aplicativo. Você também precisará adicionar um método manipulador RootFrame_NavigationFailed para lidar com quaisquer erros que ocorram durante o carregamento de páginas.

    protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
    {
        m_window = new MainWindow();
        var rootFrame = new Frame();
        rootFrame.NavigationFailed += RootFrame_NavigationFailed;
        rootFrame.Navigate(typeof(MainPage), args);
        m_window.Content = rootFrame;
        m_window.Activate();
    }
    
    private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
    {
        throw new Exception($"Error loading page {e.SourcePageType.FullName}");
    }
    
  • Você também precisará adicionar quatro instruções de utilização à parte superior do arquivo App.xaml.cs para resolver erros de compilação no código.

    using Microsoft.UI.Xaml.Controls;
    using Microsoft.UI.Xaml.Navigation;
    using System;
    using WindowsHelloLogin.Views;
    
  • Clique com o botão direito do mouse na nova pasta Exibições , selecione Adicionar>Novo Item e selecione o modelo Página em Branco . Nomeie esta página como "Login.xaml".

  • Para definir a interface do usuário para a nova página de logon, adicione o seguinte XAML. Este XAML define a StackPanel para alinhar os seguintes filhos:

    • TextBlock que irá conter um título.

    • A TextBlock para mensagens de erro.

    • A TextBox para o nome de usuário a ser inserido.

    • A Button para navegar até uma página de registo.

    • A TextBlock destinada a conter o status do Windows Hello.

    • A TextBlock para explicar a página de login, pois ainda não há backend ou utilizadores configurados.

      <Grid>
        <StackPanel>
          <TextBlock Text="Login" FontSize="36" Margin="4" TextAlignment="Center"/>
          <TextBlock x:Name="ErrorMessage" Text="" FontSize="20" Margin="4" Foreground="Red" TextAlignment="Center"/>
          <TextBlock Text="Enter your username below" Margin="0,0,0,20"
                     TextWrapping="Wrap" Width="300"
                     TextAlignment="Center" VerticalAlignment="Center" FontSize="16"/>
          <TextBox x:Name="UsernameTextBox" Margin="4" Width="250"/>
          <Button x:Name="LoginButton" Content="Login" Background="DodgerBlue" Foreground="White"
                  Click="LoginButton_Click" Width="80" HorizontalAlignment="Center" Margin="0,20"/>
          <TextBlock Text="Don't have an account?"
                     TextAlignment="Center" VerticalAlignment="Center" FontSize="16"/>
          <TextBlock x:Name="RegisterButtonTextBlock" Text="Register now"
                     PointerPressed="RegisterButtonTextBlock_OnPointerPressed"
                     Foreground="DodgerBlue"
                     TextAlignment="Center" VerticalAlignment="Center" FontSize="16"/>
          <Border x:Name="WindowsHelloStatus" Background="#22B14C"
                  Margin="0,20" Height="100" >
            <TextBlock x:Name="WindowsHelloStatusText" Text="Windows Hello is ready to use!"
                       Margin="4" TextAlignment="Center" VerticalAlignment="Center" FontSize="20"/>
          </Border>
          <TextBlock x:Name="LoginExplanation" FontSize="24" TextAlignment="Center" TextWrapping="Wrap" 
                     Text="Please Note: To demonstrate a login, validation will only occur using the default username 'sampleUsername'"/>
        </StackPanel>
      </Grid>
      
  • Alguns métodos precisam ser adicionados ao arquivo code-behind para compilar a solução. Pressione F7 ou use o Gerenciador de Soluções para editar o arquivo Login.xaml.cs. Adicione os dois métodos de evento a seguir para manipular os eventos Login e Register . Por enquanto, esses métodos definirão o ErrorMessage.Text para uma cadeia de caracteres vazia. Certifique-se de incluir as declarações using a seguir. Serão necessários para as próximas etapas.

    using Microsoft.UI.Xaml;
    using Microsoft.UI.Xaml.Controls;
    using Microsoft.UI.Xaml.Input;
    using Microsoft.UI.Xaml.Media;
    using Microsoft.UI.Xaml.Navigation;
    using System;
    
    namespace WindowsHelloLogin.Views
    {
        public sealed partial class Login : Page
        {
            public Login()
            {
                this.InitializeComponent();
            }
            private void LoginButton_Click(object sender, RoutedEventArgs e)
            {
                ErrorMessage.Text = "";
            }
            private void RegisterButtonTextBlock_OnPointerPressed(object sender, PointerRoutedEventArgs e)
            {
                ErrorMessage.Text = "";
            }
        }
    }
    
  • Para renderizar a Página de Login, edite o código da Página Principal para redirecionar para a Página de Login quando o Página Principal for carregado. Abra o arquivo MainPage.xaml.cs. No Gerenciador de Soluções, clique duas vezes em MainPage.xaml.cs. Se você não conseguir encontrar isso, clique na pequena seta ao lado de MainPage.xaml para mostrar o arquivo code-behind. Crie um método de manipulador de eventos Loaded que navegará até a página Login .

    namespace WindowsHelloLogin.Views
    {
        public sealed partial class MainPage : Page
        {
            public MainPage()
            {
                this.InitializeComponent();
                Loaded += MainPage_Loaded;
            }
    
            private void MainPage_Loaded(object sender, RoutedEventArgs e)
            {
                Frame.Navigate(typeof(Login));
            }
        }
    }
    
  • Na página Logon , você precisa manipular o OnNavigatedTo evento para validar se o Windows Hello está disponível na máquina atual. No Login.xaml.cs, implemente o código a seguir. Você notará que o objeto WindowsHelloHelper indica que há um erro. Isso porque ainda não criamos essa classe auxiliar.

    public sealed partial class Login : Page
    {
        public Login()
        {
            this.InitializeComponent();
        }
    
        protected override async void OnNavigatedTo(NavigationEventArgs e)
        {
            // Check if Windows Hello is set up and available on this machine
            if (await WindowsHelloHelper.WindowsHelloAvailableCheckAsync())
            {
            }
            else
            {
                // Windows Hello isn't set up, so inform the user
                WindowsHelloStatus.Background = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 50, 170, 207));
                WindowsHelloStatusText.Text = $"Windows Hello is not set up!{Environment.NewLine}Please go to Windows Settings and set up a PIN to use it.";
                LoginButton.IsEnabled = false;
            }
        }
    }
    
  • Para criar a classe WindowsHelloHelper, clique com o botão direito do mouse no projeto WindowsHelloLogin e clique em Adicionar>Nova Pasta. Nomeie esta pasta como Utils.

  • Clique com o botão direito do mouse na pasta Utils e selecione Adicionar>classe. Nomeie essa nova classe como "WindowsHelloHelper.cs".

    Uma captura de tela da criação da classe auxiliar de login do Windows Hello

  • Altere o escopo da classe WindowsHelloHelper para ser public statice, em seguida, adicione o seguinte método para informar o usuário se o Windows Hello está pronto para ser usado ou não. Você precisará adicionar os namespaces necessários.

    using System;
    using System.Diagnostics;
    using System.Threading.Tasks;
    using Windows.Security.Credentials;
    
    namespace WindowsHelloLogin.Utils
    {
        public static class WindowsHelloHelper
        {
            /// <summary>
            /// Checks to see if Windows Hello is ready to be used.
            /// 
            /// Windows Hello has dependencies on:
            ///     1. Having a connected Microsoft Account
            ///     2. Having a Windows PIN set up for that account on the local machine
            /// </summary>
            public static async Task<bool> WindowsHelloAvailableCheckAsync()
            {
                bool keyCredentialAvailable = await KeyCredentialManager.IsSupportedAsync();
                if (keyCredentialAvailable == false)
                {
                    // Key credential is not enabled yet as user 
                    // needs to connect to a Microsoft Account and select a PIN in the connecting flow.
                    Debug.WriteLine("Windows Hello is not set up!\nPlease go to Windows Settings and set up a PIN to use it.");
                    return false;
                }
    
                return true;
            }
        }
    }
    
  • Em Login.xaml.cs, adicione uma referência ao WindowsHelloLogin.Utils namespace. Isto resolverá o erro no método OnNavigatedTo.

    using WindowsHelloLogin.Utils;
    
  • Compile e execute o aplicativo. Você será navegado até a página de login e o banner do Windows Hello indicará se o Windows Hello está pronto para ser usado. Você deve ver o banner verde ou azul indicando o status do Windows Hello em sua máquina.

    Uma captura de ecrã do ecrã de início de sessão do Windows Hello com o estado pronto

  • A próxima coisa que você precisa fazer é criar a lógica para entrar. Crie uma nova pasta no projeto chamada "Modelos".

  • Na pasta Modelos , crie uma nova classe chamada "Account.cs". Esta classe funcionará como o seu modelo de conta. Como este é um projeto de exemplo, ele conterá apenas um nome de usuário. Altere o escopo da classe para public e adicione a Username propriedade.

    namespace WindowsHelloLogin.Models
    {
        public class Account
        {
            public string Username { get; set; }
        }
    }
    
  • O aplicativo precisa de uma maneira de lidar com contas. Para este laboratório prático, como não há servidor ou banco de dados, uma lista de usuários é salva e carregada localmente. Clique com o botão direito do mouse na pasta Utils e adicione uma nova classe chamada "AccountHelper.cs". Altere o escopo da classe para .public static O AccountHelper é uma classe estática que contém todos os métodos necessários para salvar e carregar a lista de contas localmente. O processo de salvar e carregar funciona usando um XmlSerializer. Você também precisa lembrar o arquivo que foi salvo e onde você o salvou.

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    using System.Threading.Tasks;
    using System.Xml.Serialization;
    using Windows.Storage;
    using WindowsHelloLogin.Models;
    
    namespace WindowsHelloLogin.Utils
    {
        public static class AccountHelper
        {
            // In the real world this would not be needed as there would be a server implemented that would host a user account database.
            // For this tutorial we will just be storing accounts locally.
            private const string USER_ACCOUNT_LIST_FILE_NAME = "accountlist.txt";
            private static string _accountListPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, USER_ACCOUNT_LIST_FILE_NAME);
            public static List<Account> AccountList = [];
    
            /// <summary>
            /// Create and save a useraccount list file. (Updating the old one)
            /// </summary>
            private static async void SaveAccountListAsync()
            {
                string accountsXml = SerializeAccountListToXml();
    
                if (File.Exists(_accountListPath))
                {
                    StorageFile accountsFile = await StorageFile.GetFileFromPathAsync(_accountListPath);
                    await FileIO.WriteTextAsync(accountsFile, accountsXml);
                }
                else
                {
                    StorageFile accountsFile = await ApplicationData.Current.LocalFolder.CreateFileAsync(USER_ACCOUNT_LIST_FILE_NAME);
                    await FileIO.WriteTextAsync(accountsFile, accountsXml);
                }
            }
    
            /// <summary>
            /// Gets the useraccount list file and deserializes it from XML to a list of useraccount objects.
            /// </summary>
            /// <returns>List of useraccount objects</returns>
            public static async Task<List<Account>> LoadAccountListAsync()
            {
                if (File.Exists(_accountListPath))
                {
                    StorageFile accountsFile = await StorageFile.GetFileFromPathAsync(_accountListPath);
    
                    string accountsXml = await FileIO.ReadTextAsync(accountsFile);
                    DeserializeXmlToAccountList(accountsXml);
                }
    
                return AccountList;
            }
    
            /// <summary>
            /// Uses the local list of accounts and returns an XML formatted string representing the list
            /// </summary>
            /// <returns>XML formatted list of accounts</returns>
            public static string SerializeAccountListToXml()
            {
                var xmlizer = new XmlSerializer(typeof(List<Account>));
                var writer = new StringWriter();
                xmlizer.Serialize(writer, AccountList);
    
                return writer.ToString();
            }
    
            /// <summary>
            /// Takes an XML formatted string representing a list of accounts and returns a list object of accounts
            /// </summary>
            /// <param name="listAsXml">XML formatted list of accounts</param>
            /// <returns>List object of accounts</returns>
            public static List<Account> DeserializeXmlToAccountList(string listAsXml)
            {
                var xmlizer = new XmlSerializer(typeof(List<Account>));
                TextReader textreader = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(listAsXml)));
    
                return AccountList = (xmlizer.Deserialize(textreader)) as List<Account>;
            }
        }
    }
    
  • Em seguida, implemente uma maneira de adicionar e remover uma conta da lista local de contas. Cada uma dessas ações salvará a lista. O método final que você precisará para este laboratório prático é um método de validação. Como não há nenhum servidor de autorização ou banco de dados de utilizadores, a validação será feita com base num único utilizador, cujo nome está fixo no código. Esses métodos devem ser adicionados à classe AccountHelper .

    public static Account AddAccount(string username)
    {
        // Create a new account with the username
        var account = new Account() { Username = username };
        // Add it to the local list of accounts
        AccountList.Add(account);
        // SaveAccountList and return the account
        SaveAccountListAsync();
        return account;
    }
    
    public static void RemoveAccount(Account account)
    {
        // Remove the account from the accounts list
        AccountList.Remove(account);
        // Re save the updated list
        SaveAccountListAsync();
    }
    
    public static bool ValidateAccountCredentials(string username)
    {
        // In the real world, this method would call the server to authenticate that the account exists and is valid.
        // However, for this tutorial, we'll just have an existing sample user that's named "sampleUsername".
        // If the username is null or does not match "sampleUsername" validation will fail. 
        // In this case, the user should register a new Windows Hello user.
    
        if (string.IsNullOrEmpty(username))
        {
            return false;
        }
    
        if (!string.Equals(username, "sampleUsername"))
        {
            return false;
        }
    
        return true;
    }
    
  • A próxima coisa que você precisa fazer é lidar com uma solicitação de entrada do usuário. No Login.xaml.cs, crie uma nova variável privada que manterá o login da conta atual. Em seguida, adicione um novo método chamado SignInWindowsHelloAsync. Isso validará as credenciais da conta usando o método AccountHelper.ValidateAccountCredentials . Esse método retornará um valor booleano se o nome de usuário inserido for o mesmo que o valor da cadeia de caracteres codificada que você configurou na etapa anterior. O valor codificado para este exemplo é "sampleUsername".

    using WindowsHelloLogin.Models;
    using WindowsHelloLogin.Utils;
    using System.Diagnostics;
    using System.Threading.Tasks;
    
    namespace WindowsHelloLogin.Views
    {
        public sealed partial class Login : Page
        {
            private Account _account;
    
            public Login()
            {
                this.InitializeComponent();
            }
    
            protected override async void OnNavigatedTo(NavigationEventArgs e)
            {
                // Check if Windows Hello is set up and available on this machine
                if (await WindowsHelloHelper.WindowsHelloAvailableCheckAsync())
                {
                }
                else
                {
                    // Windows Hello is not set up, so inform the user
                    WindowsHelloStatus.Background = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 50, 170, 207));
                    WindowsHelloStatusText.Text = "Windows Hello is not set up!\nPlease go to Windows Settings and set up a PIN to use it.";
                    LoginButton.IsEnabled = false;
                }
            }
    
            private async void LoginButton_Click(object sender, RoutedEventArgs e)
            {
                ErrorMessage.Text = "";
                await SignInWindowsHelloAsync();
            }
    
            private void RegisterButtonTextBlock_OnPointerPressed(object sender, PointerRoutedEventArgs e)
            {
                ErrorMessage.Text = "";
            }
    
            private async Task SignInWindowsHelloAsync()
            {
                if (AccountHelper.ValidateAccountCredentials(UsernameTextBox.Text))
                {
                    // Create and add a new local account
                    _account = AccountHelper.AddAccount(UsernameTextBox.Text);
                    Debug.WriteLine("Successfully signed in with traditional credentials and created local account instance!");
    
                    //if (await WindowsHelloHelper.CreateWindowsHelloKeyAsync(UsernameTextBox.Text))
                    //{
                    //    Debug.WriteLine("Successfully signed in with Windows Hello!");
                    //}
                }
                else
                {
                    ErrorMessage.Text = "Invalid Credentials";
                }
            }
        }
    }
    
  • Você pode ter notado o código comentado que estava fazendo referência a um método no WindowsHelloHelper. No WindowsHelloHelper.cs, adicione um novo método chamado CreateWindowsHelloKeyAsync. Esse método usa a API do Windows Hello no KeyCredentialManager. Chamar RequestCreateAsync criará uma chave do Windows Hello específica para accountId e a máquina local. Observe os comentários na declaração de mudança se você estiver interessado em implementar isso em um cenário do mundo real.

    /// <summary>
    /// Creates a Windows Hello key on the machine using the account ID provided.
    /// </summary>
    /// <param name="accountId">The account ID associated with the account that we are enrolling into Windows Hello</param>
    /// <returns>Boolean indicating if creating the Windows Hello key succeeded</returns>
    public static async Task<bool> CreateWindowsHelloKeyAsync(string accountId)
    {
        KeyCredentialRetrievalResult keyCreationResult = await KeyCredentialManager.RequestCreateAsync(accountId, KeyCredentialCreationOption.ReplaceExisting);
    
        switch (keyCreationResult.Status)
        {
            case KeyCredentialStatus.Success:
                Debug.WriteLine("Successfully created key");
    
                // In the real world, authentication would take place on a server.
                // So, every time a user migrates or creates a new Windows Hello
                // account, details should be pushed to the server.
                // The details that would be pushed to the server include:
                // The public key, keyAttestation (if available), 
                // certificate chain for attestation endorsement key (if available),  
                // status code of key attestation result: keyAttestationIncluded or 
                // keyAttestationCanBeRetrievedLater and keyAttestationRetryType.
                // As this sample has no concept of a server, it will be skipped for now.
                // For information on how to do this, refer to the second sample.
    
                // For this sample, just return true
                return true;
            case KeyCredentialStatus.UserCanceled:
                Debug.WriteLine("User cancelled sign-in process.");
                break;
            case KeyCredentialStatus.NotFound:
                // User needs to set up Windows Hello
                Debug.WriteLine("Windows Hello is not set up!\nPlease go to Windows Settings and set up a PIN to use it.");
                break;
            default:
                break;
        }
    
        return false;
    }
    
  • Agora que você criou o método CreateWindowsHelloKeyAsync, retorne ao arquivo Login.xaml.cs e descomente o código dentro do método SignInWindowsHelloAsync.

    private async void SignInWindowsHelloAsync()
    {
        if (AccountHelper.ValidateAccountCredentials(UsernameTextBox.Text))
        {
            //Create and add a new local account
            _account = AccountHelper.AddAccount(UsernameTextBox.Text);
            Debug.WriteLine("Successfully signed in with traditional credentials and created local account instance!");
    
            if (await WindowsHelloHelper.CreateWindowsHelloKeyAsync(UsernameTextBox.Text))
            {
                Debug.WriteLine("Successfully signed in with Windows Hello!");
            }
        }
        else
        {
            ErrorMessage.Text = "Invalid Credentials";
        }
    }
    
  • Compile e execute o aplicativo. Você será levado para a página de login. Digite o nome de usuário como "sampleUsername" e clique em login. Ser-lhe-á apresentado um pedido do Windows Hello para introduzir o seu PIN. Ao inserir seu PIN corretamente, o método CreateWindowsHelloKeyAsync poderá criar uma chave do Windows Hello. Monitore as janelas de saída para ver se as mensagens que indicam sucesso são mostradas.

    Uma captura de ecrã do prompt de código PIN do Windows Hello

Exercício 2: Páginas de boas-vindas e seleção de usuários

No presente exercício, continuarás a partir do exercício anterior. Quando um utilizador inicia sessão com sucesso, deve ser encaminhado para uma página de boas-vindas onde pode terminar sessão ou eliminar a sua conta. Como o Windows Hello cria uma chave para cada máquina, uma tela de seleção de usuário pode ser criada, que exibe todos os usuários que entraram nessa máquina. Um usuário pode então selecionar uma dessas contas e ir diretamente para a tela de boas-vindas sem a necessidade de digitar novamente uma senha, pois já se autenticou para acessar a máquina.

  • Na pasta Views , adicione uma nova página em branco chamada "Welcome.xaml". Adicione o seguinte XAML para concluir a interface do usuário da página. Isso exibirá um título, o nome de usuário conectado e dois botões. Um dos botões navegará de volta para uma lista de usuários (que você criará mais tarde), e o outro botão lidará com o esquecimento desse usuário.

    <Grid>
      <StackPanel>
        <TextBlock x:Name="Title" Text="Welcome" FontSize="40" TextAlignment="Center"/>
        <TextBlock x:Name="UserNameText" FontSize="28" TextAlignment="Center"/>
    
        <Button x:Name="BackToUserListButton" Content="Back to User List" Click="Button_Restart_Click"
                HorizontalAlignment="Center" Margin="0,20" Foreground="White" Background="DodgerBlue"/>
    
        <Button x:Name="ForgetButton" Content="Forget Me" Click="Button_Forget_User_Click"
                Foreground="White"
                Background="Gray"
                HorizontalAlignment="Center"/>
      </StackPanel>
    </Grid>
    
  • No Welcome.xaml.cs arquivo code-behind, adicione uma nova variável privada que manterá a conta conectada. Você precisará implementar um método para substituir o OnNavigateTo evento, isso armazenará a conta passada para a página de boas-vindas . Você também precisará implementar o Click evento para os dois botões definidos no XAML. Você precisará adicionar instruções using para os namespaces WindowsHelloLogin.Models e WindowsHelloLogin.Utils.

    using WindowsHelloLogin.Models;
    using WindowsHelloLogin.Utils;
    using System.Diagnostics;
    
    namespace WindowsHelloLogin.Views
    {
        public sealed partial class Welcome : Page
        {
            private Account _activeAccount;
    
            public Welcome()
            {
                InitializeComponent();
            }
    
            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                _activeAccount = (Account)e.Parameter;
                if (_activeAccount != null)
                {
                    UserNameText.Text = _activeAccount.Username;
                }
            }
    
            private void Button_Restart_Click(object sender, RoutedEventArgs e)
            {
            }
    
            private void Button_Forget_User_Click(object sender, RoutedEventArgs e)
            {
                // Remove the account from Windows Hello
                // WindowsHelloHelper.RemoveWindowsHelloAccountAsync(_activeAccount);
    
                // Remove it from the local accounts list and re-save the updated list
                AccountHelper.RemoveAccount(_activeAccount);
    
                Debug.WriteLine($"User {_activeAccount.Username} deleted.");
            }
        }
    }
    
  • Você pode ter notado uma linha comentada no manipulador de eventos Button_Forget_User_Click. A conta está sendo removida da sua lista local, mas atualmente não há nenhuma maneira de ser removida do Windows Hello. Você precisa implementar um novo método no WindowsHelloHelper.cs que lidará com a remoção de um usuário do Windows Hello. Esse método usará outras APIs do Windows Hello para abrir e excluir a conta. No mundo real, quando você exclui uma conta, o servidor ou banco de dados deve ser notificado para que o banco de dados do usuário permaneça válido. Você precisará de uma instrução 'using' que faça referência ao namespace WindowsHelloLogin.Models.

    using WindowsHelloLogin.Models;
    
    /// <summary>
    /// Function to be called when user requests deleting their account.
    /// Checks the KeyCredentialManager to see if there is a Windows Hello
    /// account for the current user.
    /// It then deletes the local key associated with the account.
    /// </summary>
    public static async void RemoveWindowsHelloAccountAsync(Account account)
    {
        // Open the account with Windows Hello
        KeyCredentialRetrievalResult keyOpenResult = await KeyCredentialManager.OpenAsync(account.Username);
    
        if (keyOpenResult.Status == KeyCredentialStatus.Success)
        {
            // In the real world you would send key information to server to unregister
            //for example, RemoveWindowsHelloAccountOnServer(account);
        }
    
        // Then delete the account from the machine's list of Windows Hello accounts
        await KeyCredentialManager.DeleteAsync(account.Username);
    }
    
  • De volta ao Welcome.xaml.cs, descomente a linha que chama RemoveWindowsHelloAccountAsync.

    private void Button_Forget_User_Click(object sender, RoutedEventArgs e)
    {
        // Remove it from Windows Hello
        WindowsHelloHelper.RemoveWindowsHelloAccountAsync(_activeAccount);
    
        // Remove it from the local accounts list and re-save the updated list
        AccountHelper.RemoveAccount(_activeAccount);
    
        Debug.WriteLine($"User {_activeAccount.Username} deleted.");
    }
    
  • No método SignInWindowsHelloAsync (em Login.xaml.cs), quando o CreateWindowsHelloKeyAsync for bem-sucedido, ele deverá navegar até a página Welcome e passar a Account.

    private async void SignInWindowsHelloAsync()
    {
        if (AccountHelper.ValidateAccountCredentials(UsernameTextBox.Text))
        {
            // Create and add a new local account
            _account = AccountHelper.AddAccount(UsernameTextBox.Text);
            Debug.WriteLine("Successfully signed in with traditional credentials and created local account instance!");
    
            if (await WindowsHelloHelper.CreateWindowsHelloKeyAsync(UsernameTextBox.Text))
            {
                Debug.WriteLine("Successfully signed in with Windows Hello!");
                Frame.Navigate(typeof(Welcome), _account);
            }
        }
        else
        {
            ErrorMessage.Text = "Invalid Credentials";
        }
    }
    
  • Compile e execute o aplicativo. Faça login com "sampleUsername" e clique em Login. Introduza o seu PIN e, se for bem-sucedido, deverá ser navegado para o ecrã de boas-vindas . Tente clicar em Esquecer usuário e monitorar a janela de saída do Visual Studio para ver se o usuário foi excluído. Observe que, quando o usuário é excluído, você permanece na página de boas-vindas . Você precisará criar uma página de seleção de usuário para a qual o aplicativo possa navegar.

    Uma captura de ecrã do ecrã de boas-vindas do Windows Hello

  • Na pasta Views , crie uma nova página em branco chamada "UserSelection.xaml" e adicione o seguinte XAML para definir a interface do usuário. Esta página conterá um ListView que exibe todos os usuários na lista de contas locais e um Button que navegará até a página de login para permitir que o usuário adicione outra conta.

    <Grid>
      <StackPanel>
        <TextBlock x:Name="Title" Text="Select a User" FontSize="36" Margin="4" TextAlignment="Center" HorizontalAlignment="Center"/>
    
        <ListView x:Name="UserListView" Margin="4" MaxHeight="200" MinWidth="250" Width="250" HorizontalAlignment="Center">
          <ListView.ItemTemplate>
            <DataTemplate>
              <Grid Background="DodgerBlue" Height="50" Width="250" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                <TextBlock Text="{Binding Username}" HorizontalAlignment="Center" TextAlignment="Center" VerticalAlignment="Center" Foreground="White"/>
              </Grid>
            </DataTemplate>
          </ListView.ItemTemplate>
        </ListView>
    
        <Button x:Name="AddUserButton" Content="+" FontSize="36" Width="60" Click="AddUserButton_Click" HorizontalAlignment="Center"/>
      </StackPanel>
    </Grid>
    
  • Em UserSelection.xaml.cs, implemente o Loaded método que navegará até a página de login se não houver contas na lista local. Implemente também o SelectionChanged evento para o ListView e um Click evento para o Button.

    using System.Diagnostics;
    using WindowsHelloLogin.Models;
    using WindowsHelloLogin.Utils;
    
    namespace WindowsHelloLogin.Views
    {
        public sealed partial class UserSelection : Page
        {
            public UserSelection()
            {
                InitializeComponent();
                Loaded += UserSelection_Loaded;
            }
    
            private void UserSelection_Loaded(object sender, RoutedEventArgs e)
            {
                if (AccountHelper.AccountList.Count == 0)
                {
                    // If there are no accounts, navigate to the Login page
                    Frame.Navigate(typeof(Login));
                }
    
    
                UserListView.ItemsSource = AccountHelper.AccountList;
                UserListView.SelectionChanged += UserSelectionChanged;
            }
    
            /// <summary>
            /// Function called when an account is selected in the list of accounts
            /// Navigates to the Login page and passes the chosen account
            /// </summary>
            private void UserSelectionChanged(object sender, RoutedEventArgs e)
            {
                if (((ListView)sender).SelectedValue != null)
                {
                    Account account = (Account)((ListView)sender).SelectedValue;
                    if (account != null)
                    {
                        Debug.WriteLine($"Account {account.Username} selected!");
                    }
                    Frame.Navigate(typeof(Login), account);
                }
            }
    
            /// <summary>
            /// Function called when the "+" button is clicked to add a new user.
            /// Navigates to the Login page with nothing filled out
            /// </summary>
            private void AddUserButton_Click(object sender, RoutedEventArgs e)
            {
                Frame.Navigate(typeof(Login));
            }
        }
    }
    
  • Há alguns lugares no aplicativo onde você deseja navegar até a página UserSelection . No MainPage.xaml.cs, você deve navegar até a página UserSelection em vez da página Login . Enquanto você estiver no evento carregado na MainPage, você precisará carregar a lista de contas para que a página UserSelection possa verificar se há contas. Isso exigirá alterar o método Loaded para ser assíncrono e também adicionar uma declaração using para o namespace WindowsHelloLogin.Utils.

    using WindowsHelloLogin.Utils;
    
    private async void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        // Load the local account list before navigating to the UserSelection page
        await AccountHelper.LoadAccountListAsync();
        Frame.Navigate(typeof(UserSelection));
    }
    
  • Em seguida, o aplicativo precisará navegar até a página UserSelection na página Bem-vindo . Em ambos os Click eventos, você deve navegar de volta para a página UserSelection .

    private void Button_Restart_Click(object sender, RoutedEventArgs e)
    {
        Frame.Navigate(typeof(UserSelection));
    }
    
    private void Button_Forget_User_Click(object sender, RoutedEventArgs e)
    {
        // Remove it from Windows Hello
        WindowsHelloHelper.RemoveWindowsHelloAccountAsync(_activeAccount);
    
        // Remove it from the local accounts list and re-save the updated list
        AccountHelper.RemoveAccount(_activeAccount);
    
        Debug.WriteLine($"User {_activeAccount.Username} deleted.");
    
        // Navigate back to UserSelection page.
        Frame.Navigate(typeof(UserSelection));
    }
    
  • Na página Login , você precisa de código para fazer login na conta selecionada na lista na página UserSelection . No evento OnNavigatedTo, armazene a conta passada durante a navegação. Comece adicionando uma nova variável privada que identificará se a conta é uma conta existente. Em seguida, manipule o evento OnNavigatedTo.

    namespace WindowsHelloLogin.Views
    {
        public sealed partial class Login : Page
        {
            private Account _account;
            private bool _isExistingAccount;
    
            public Login()
            {
                InitializeComponent();
            }
    
            /// <summary>
            /// Function called when this frame is navigated to.
            /// Checks to see if Windows Hello is available and if an account was passed in.
            /// If an account was passed in set the "_isExistingAccount" flag to true and set the _account.
            /// </summary>
            protected override async void OnNavigatedTo(NavigationEventArgs e)
            {
                // Check Windows Hello is set up and available on this machine
                if (await WindowsHelloHelper.WindowsHelloAvailableCheckAsync())
                {
                    if (e.Parameter != null)
                    {
                        _isExistingAccount = true;
                        // Set the account to the existing account being passed in
                        _account = (Account)e.Parameter;
                        UsernameTextBox.Text = _account.Username;
                        await SignInWindowsHelloAsync();
                    }
                }
                else
                {
                    // Windows Hello is not set up, so inform the user
                    WindowsHelloStatus.Background = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 50, 170, 207));
                    WindowsHelloStatusText.Text = $"Windows Hello is not set up!{Environment.NewLine}Please go to Windows Settings and set up a PIN to use it.";
                    LoginButton.IsEnabled = false;
                }
            }
        }
    }
    
  • O método SignInWindowsHelloAsync precisará ser atualizado para entrar na conta selecionada. O WindowsHelloHelper precisará de outro método para abrir a conta com o Windows Hello, pois a conta já tem uma chave de conta criada para ele. Implemente o novo método no WindowsHelloHelper.cs para entrar em um usuário existente com o Windows Hello. Para obter informações sobre cada parte do código, leia os comentários do código.

    /// <summary>
    /// Attempts to sign a message using the account key on the system for the accountId passed.
    /// </summary>
    /// <returns>Boolean representing if creating the Windows Hello authentication message succeeded</returns>
    public static async Task<bool> GetWindowsHelloAuthenticationMessageAsync(Account account)
    {
        KeyCredentialRetrievalResult openKeyResult = await KeyCredentialManager.OpenAsync(account.Username);
        // Calling OpenAsync will allow the user access to what is available in the app and will not require user credentials again.
        // If you wanted to force the user to sign in again you can use the following:
        // var consentResult = await Windows.Security.Credentials.UI.UserConsentVerifier.RequestVerificationAsync(account.Username);
        // This will ask for the either the password of the currently signed in Microsoft Account or the PIN used for Windows Hello.
    
        if (openKeyResult.Status == KeyCredentialStatus.Success)
        {
            // If OpenAsync has succeeded, the next thing to think about is whether the client application requires access to backend services.
            // If it does here you would request a challenge from the server. The client would sign this challenge and the server
            // would check the signed challenge. If it is correct, it would allow the user access to the backend.
            // You would likely make a new method called RequestSignAsync to handle all this.
            // For example, RequestSignAsync(openKeyResult);
            // Refer to the second Windows Hello sample for information on how to do this.
    
            // For this sample, there is not concept of a server implemented so just return true.
            return true;
        }
        else if (openKeyResult.Status == KeyCredentialStatus.NotFound)
        {
            // If the account is not found at this stage. It could be one of two errors. 
            // 1. Windows Hello has been disabled
            // 2. Windows Hello has been disabled and re-enabled cause the Windows Hello Key to change.
            // Calling CreateWindowsHelloKeyAsync and passing through the account will attempt to replace the existing Windows Hello Key for that account.
            // If the error really is that Windows Hello is disabled then the CreateWindowsHelloKeyAsync method will output that error.
            if (await CreateWindowsHelloKeyAsync(account.Username))
            {
                // If the Hello Key was again successfully created, Windows Hello has just been reset.
                // Now that the Hello Key has been reset for the account retry sign in.
                return await GetWindowsHelloAuthenticationMessageAsync(account);
            }
        }
    
        // Can't use Windows Hello right now, try again later
        return false;
    }
    
  • Atualize o método SignInWindowsHelloAsync no Login.xaml.cs para manipular a conta existente. Isso usará o novo método no WindowsHelloHelper.cs. Se for bem-sucedida, a conta será conectada e o usuário navegará até a página de boas-vindas .

    private async Task SignInWindowsHelloAsync()
    {
        if (_isExistingAccount)
        {
            if (await WindowsHelloHelper.GetWindowsHelloAuthenticationMessageAsync(_account))
            {
                Frame.Navigate(typeof(Welcome), _account);
            }
        }
        else if (AccountHelper.ValidateAccountCredentials(UsernameTextBox.Text))
        {
            //Create and add a new local account
            _account = AccountHelper.AddAccount(UsernameTextBox.Text);
            Debug.WriteLine("Successfully signed in with traditional credentials and created local account instance!");
    
            if (await WindowsHelloHelper.CreateWindowsHelloKeyAsync(UsernameTextBox.Text))
            {
                Debug.WriteLine("Successfully signed in with Windows Hello!");
                Frame.Navigate(typeof(Welcome), _account);
            }
        }
        else
        {
            ErrorMessage.Text = "Invalid Credentials";
        }
    }
    
  • Compile e execute o aplicativo. Inicie sessão com "sampleUsername". Introduza o seu PIN e, se for bem-sucedido, será navegado para a página de boas-vindas . Clique em voltar à lista de usuários. Agora você deve ver um usuário na lista. Se clicar nisto, o WindowsHello permite-lhe iniciar sessão novamente sem ter de voltar a introduzir palavras-passe, etc.

    Uma captura de tela da lista de usuários selecionados do Windows Hello

Exercício 3: Registrando um novo usuário do Windows Hello

Neste exercício, você cria uma nova página que pode criar uma nova conta com o Windows Hello. Isso funciona de forma semelhante ao funcionamento da página de login . A página Login é implementada para um usuário existente que está migrando para usar o Windows Hello. Uma página WindowsHelloRegister criará o registro do Windows Hello para um novo usuário.

  • Na pasta Views , crie uma nova página em branco chamada "WindowsHelloRegister.xaml". No XAML, adicione o seguinte para configurar a interface do usuário. A interface nesta página é semelhante à página de login .

    <Grid>
      <StackPanel>
        <TextBlock x:Name="Title" Text="Register New Windows Hello User" FontSize="24" Margin="4" TextAlignment="Center"/>
    
        <TextBlock x:Name="ErrorMessage" Text="" FontSize="20" Margin="4" Foreground="Red" TextAlignment="Center"/>
    
        <TextBlock Text="Enter your new username below" Margin="0,0,0,20"
                   TextWrapping="Wrap" Width="300"
                   TextAlignment="Center" VerticalAlignment="Center" FontSize="16"/>
    
        <TextBox x:Name="UsernameTextBox" Margin="4" Width="250"/>
    
        <Button x:Name="RegisterButton" Content="Register" Background="DodgerBlue" Foreground="White"
                Click="RegisterButton_Click_Async" Width="80" HorizontalAlignment="Center" Margin="0,20"/>
    
        <Border x:Name="WindowsHelloStatus" Background="#22B14C" Margin="4" Height="100">
          <TextBlock x:Name="WindowsHelloStatusText" Text="Windows Hello is ready to use!" FontSize="20"
                     Margin="4" TextAlignment="Center" VerticalAlignment="Center"/>
        </Border>
      </StackPanel>
    </Grid>
    
  • No arquivo de code-behind WindowsHelloRegister.xaml.cs, implemente uma variável privada Account e um Click evento para o botão de registo. Isso adicionará uma nova conta local e criará uma chave do Windows Hello.

    using Microsoft.UI.Xaml.Controls;
    using Microsoft.UI.Xaml;
    using WindowsHelloLogin.Models;
    using WindowsHelloLogin.Utils;
    
    namespace WindowsHelloLogin.Views
    {
        public sealed partial class WindowsHelloRegister : Page
        {
            private Account _account;
    
            public WindowsHelloRegister()
            {
                InitializeComponent();
            }
    
            private async void RegisterButton_Click_Async(object sender, RoutedEventArgs e)
            {
                ErrorMessage.Text = "";
    
                // In the real world, you would validate the entered credentials and information before 
                // allowing a user to register a new account. 
                // For this sample, we'll skip that step and just register an account if the username is not null.
    
                if (!string.IsNullOrEmpty(UsernameTextBox.Text))
                {
                    // Register a new account
                    _account = AccountHelper.AddAccount(UsernameTextBox.Text);
                    // Register new account with Windows Hello
                    await WindowsHelloHelper.CreateWindowsHelloKeyAsync(_account.Username);
                    // Navigate to the Welcome page. 
                    Frame.Navigate(typeof(Welcome), _account);
                }
                else
                {
                    ErrorMessage.Text = "Please enter a username";
                }
            }
        }
    }
    
  • Você precisa navegar para esta página a partir da página de login quando o registro é clicado.

    private void RegisterButtonTextBlock_OnPointerPressed(object sender, PointerRoutedEventArgs e)
    {
        ErrorMessage.Text = "";
        Frame.Navigate(typeof(WindowsHelloRegister));
    }
    
  • Compile e execute o aplicativo. Tente registar um novo utilizador. Em seguida, volte para a lista de usuários e valide que você pode selecionar esse usuário e fazer login.

    Uma captura de ecrã da página de registo de novo utilizador do Windows Hello

Neste laboratório, você aprendeu as habilidades essenciais necessárias para usar a nova API do Windows Hello para autenticar usuários existentes e criar contas para novos usuários. Com esse novo conhecimento, você pode começar a remover a necessidade de os usuários lembrarem senhas para seu aplicativo, mas permanecer confiante de que seus aplicativos permanecem protegidos pela autenticação do usuário. O Windows utiliza a nova tecnologia de autenticação do Windows Hello para suportar as suas opções de início de sessão biométricas.