Erstellen eines Windows Hello-Anmeldediensts

Dies ist der zweite Teil einer vollständigen exemplarischen Vorgehensweise zur Verwendung von Windows Hello als Alternative zu herkömmlichen Authentifizierungssystemen für Benutzername und Kennwort in verpackten Windows-Apps. In diesem Artikel wird erläutert, wo Teil 1, Windows Hello-Anmelde-App, aufgehört und die Funktionalität erweitert wird, um zu veranschaulichen, wie Sie Windows Hello in Ihre vorhandene Anwendung integrieren können.

Um dieses Projekt zu erstellen, benötigen Sie einige Erfahrungen mit C# und XAML. Außerdem müssen Sie Visual Studio 2022 auf einem Windows 10- oder Windows 11-Computer verwenden. Ausführliche Anweisungen zum Einrichten Ihrer Entwicklungsumgebung finden Sie unter "Erste Schritte mit WinUI ".

Übung 1: Serverseitige Logik

In dieser Übung beginnen Sie mit der in der ersten Übung integrierten Windows Hello-Anwendung und erstellen einen lokalen Pseudoserver und eine lokale Datenbank. Diese praktische Übung wurde entwickelt, um zu vermitteln, wie Windows Hello in ein vorhandenes System integriert werden kann. Durch die Verwendung eines Pseudoservers und einer Pseudodatenbank werden viele nicht zusammenhängende Setups eliminiert. In Ihren eigenen Anwendungen müssen Sie die simulierten Objekte durch die realen Dienste und Datenbanken ersetzen.

  • Öffnen Sie zunächst die WindowsHelloLogin-Lösung aus dem ersten Windows Hello Hands On Lab.

  • Beginnen Sie mit der Implementierung des Pseudoservers und der Pseudodatenbank. Erstellen Sie einen neuen Ordner namens "AuthService". Klicken Sie in Projektmappen-Explorer mit der rechten Maustaste auf das WindowsHelloLogin-Projekt, und wählen Sie "Neuen Ordner hinzufügen>" aus.

  • Erstellen Sie UserAccount - und WindowsHelloDevices-Klassen , die als Modelle fungieren, damit Daten in der Pseudodatenbank gespeichert werden. Das Benutzerkonto ähnelt dem auf einem herkömmlichen Authentifizierungsserver implementierten Benutzermodell. Klicken Sie mit der rechten Maustaste auf den Ordner "AuthService ", und fügen Sie eine neue Klasse namens "UserAccount" hinzu.

    Screenshot der Erstellung des Windows Hello-Autorisierungsordners

    Screenshot des Erstellens der neuen Klasse für die Windows Hello-Benutzerautorisierung

  • Ändern Sie den Klassenbereich so, dass er öffentlich ist, und fügen Sie die folgenden öffentlichen Eigenschaften für die UserAccount-Klasse hinzu. Sie müssen eine using-Anweisung für den System.ComponentModel.DataAnnotations Namespace hinzufügen.

    using System;
    using System.ComponentModel.DataAnnotations;
    
    namespace WindowsHelloLogin.AuthService
    {
        public class UserAccount
        {
            [Key, Required]
            public Guid UserId { get; set; }
            [Required]
            public string Username { get; set; }
            public string Password { get; set; }
            // public List<WindowsHelloDevice> WindowsHelloDevices = new();
        }
    }
    

    Möglicherweise haben Sie die auskommentierte Liste von WindowsHelloDevices bemerkt. Dies ist eine Änderung, die Sie an einem vorhandenen Benutzermodell in Ihrer aktuellen Implementierung vornehmen müssen. Die Liste der WindowsHelloDevices enthält eine deviceID, den öffentlichen Schlüssel aus Windows Hello und ein KeyCredentialAttestationResult. Für diese Übung müssen Sie das keyAttestationResult implementieren, da sie nur von Windows Hello auf Geräten mit einem TPM-Chip (Trusted Platform Modules) bereitgestellt werden. Das KeyCredentialAttestationResult ist eine Kombination aus mehreren Eigenschaften und muss geteilt werden, um sie mit einer Datenbank zu speichern und zu laden.

  • Erstellen Sie eine neue Klasse im Ordner "AuthService " namens "WindowsHelloDevice.cs". Dies ist das Modell für die Windows Hello-Geräte, wie oben beschrieben. Ändern Sie den Klassenbereich so, dass er öffentlich ist, und fügen Sie die folgenden Eigenschaften hinzu.

    using System;
    
    namespace WindowsHelloLogin.AuthService
    {
        public class WindowsHelloDevice
        {
            // These are the new variables that will need to be added to the existing UserAccount in the Database
            // The DeviceName is used to support multiple devices for the one user.
            // This way the correct public key is easier to find as a new public key is made for each device.
            // The KeyAttestationResult is only used if the User device has a TPM (Trusted Platform Module) chip, 
            // in most cases it will not. So will be left out for this hands on lab.
            public Guid DeviceId { get; set; }
            public byte[] PublicKey { get; set; }
            // public KeyCredentialAttestationResult KeyAttestationResult { get; set; }
        }
    }
    
  • Kehren Sie zu UserAccount.cs zurück, und heben Sie die Kommentare aus der Liste der Windows Hello-Geräte auf.

    using System.Collections.Generic;
    
    namespace WindowsHelloLogin.AuthService
    {
        public class UserAccount
        {
            [Key, Required]
            public Guid UserId { get; set; }
            [Required]
            public string Username { get; set; }
            public string Password { get; set; }
            public List<WindowsHelloDevice> WindowsHelloDevices = new();
        }
    }
    
  • Mit dem Modell für das Benutzerkonto und dem erstellten WindowsHelloDevice müssen Sie eine weitere neue Klasse im Ordner "AuthService" erstellen, die als Pseudodatenbank fungiert, da dies eine Simuliertdatenbank ist, aus der Sie eine Liste der Benutzerkonten lokal speichern und laden. In der realen Welt wäre dies Ihre Datenbankimplementierung. Erstellen Sie eine neue Klasse im Ordner "AuthService" mit dem Namen "MockStore.cs". Ändern Sie den Klassenbereich in "öffentlich".

  • Da der simulierte Speicher eine Liste von Benutzerkonten lokal speichert und lädt, können Sie die Logik zum Speichern und Laden dieser Liste mithilfe eines XmlSerializers implementieren. Sie müssen sich auch den Dateinamen und den Speicherort merken. Implementieren Sie in MockStore.cs Folgendes:

    using System.Collections.Generic;
    using System;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Xml.Serialization;
    using Windows.Storage;
    
    namespace WindowsHelloLogin.AuthService
    {
        public class MockStore
        {
            private const string USER_ACCOUNT_LIST_FILE_NAME = "userAccountsList.txt";
            // This cannot be a const because the LocalFolder is accessed at runtime
            private string _userAccountListPath = Path.Combine(
                ApplicationData.Current.LocalFolder.Path, USER_ACCOUNT_LIST_FILE_NAME);
            private List<UserAccount> _mockDatabaseUserAccountsList;
    
    #region Save and Load Helpers
            /// <summary>
            /// Create and save a useraccount list file. (Replacing the old one)
            /// </summary>
            private async Task SaveAccountListAsync()
            {
                string accountsXml = SerializeAccountListToXml();
    
                if (File.Exists(_userAccountListPath))
                {
                    StorageFile accountsFile = await StorageFile.GetFileFromPathAsync(_userAccountListPath);
                    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>
            private async Task LoadAccountListAsync()
            {
                if (File.Exists(_userAccountListPath))
                {
                    StorageFile accountsFile = await StorageFile.GetFileFromPathAsync(_userAccountListPath);
    
                    string accountsXml = await FileIO.ReadTextAsync(accountsFile);
                    DeserializeXmlToAccountList(accountsXml);
                }
    
                // If the UserAccountList does not contain the sampleUser Initialize the sample users
                // This is only needed as it in a Hand on Lab to demonstrate a user being migrated.
                // In the real world, user accounts would just be in a database.
                if (!_mockDatabaseUserAccountsList.Any(f => f.Username.Equals("sampleUsername")))
                {
                    //If the list is empty, call InitializeSampleAccounts and return the list
                    //await InitializeSampleUserAccountsAsync();
                }
            }
    
            /// <summary>
            /// Uses the local list of accounts and returns an XML formatted string representing the list
            /// </summary>
            /// <returns>XML formatted list of accounts</returns>
            private string SerializeAccountListToXml()
            {
                var xmlizer = new XmlSerializer(typeof(List<UserAccount>));
                var writer = new StringWriter();
                xmlizer.Serialize(writer, _mockDatabaseUserAccountsList);
                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>
            private List<UserAccount> DeserializeXmlToAccountList(string listAsXml)
            {
                var xmlizer = new XmlSerializer(typeof(List<UserAccount>));
                TextReader textreader = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(listAsXml)));
                return _mockDatabaseUserAccountsList = (xmlizer.Deserialize(textreader)) as List<UserAccount>;
            }
    #endregion
        }
    }
    
  • In der LoadAccountListAsync-Methode haben Sie möglicherweise bemerkt, dass eine InitializeSampleUserAccountsAsync-Methode auskommentiert wurde. Sie müssen diese Methode im MockStore.cs erstellen. Diese Methode füllt die Liste der Benutzerkonten auf, sodass eine Anmeldung erfolgen kann. In der realen Welt wäre die Benutzerdatenbank bereits aufgefüllt. In diesem Schritt erstellen Sie auch einen Konstruktor, der die Benutzerliste initialisiert und LoadAccountListAsync aufruft.

    namespace WindowsHelloLogin.AuthService
    {
        public class MockStore
        {
            private const string USER_ACCOUNT_LIST_FILE_NAME = "userAccountsList.txt";
            // This cannot be a const because the LocalFolder is accessed at runtime
            private string _userAccountListPath = Path.Combine(
                ApplicationData.Current.LocalFolder.Path, USER_ACCOUNT_LIST_FILE_NAME);
            private List<UserAccount> _mockDatabaseUserAccountsList;
    
            public MockStore()
            {
                _mockDatabaseUserAccountsList = new List<UserAccount>();
                _ = LoadAccountListAsync();
            }
    
            private async Task InitializeSampleUserAccountsAsync()
            {
                // Create a sample Traditional User Account that only has a Username and Password
                // This will be used initially to demonstrate how to migrate to use Windows Hello
    
                var sampleUserAccount = new UserAccount()
                {
                    UserId = Guid.NewGuid(),
                    Username = "sampleUsername",
                    Password = "samplePassword",
                };
    
                // Add the sampleUserAccount to the _mockDatabase
                _mockDatabaseUserAccountsList.Add(sampleUserAccount);
                await SaveAccountListAsync();
            }
        }
    }
    
  • Da nun die InitializeSampleUserAccountsAsync-Methode vorhanden ist, heben Sie die Kommentare des Methodenaufrufs in der LoadAccountListAsync-Methode auf.

    private async Task LoadAccountListAsync()
    {
        if (File.Exists(_userAccountListPath))
        {
            StorageFile accountsFile = await StorageFile.GetFileFromPathAsync(_userAccountListPath);
    
            string accountsXml = await FileIO.ReadTextAsync(accountsFile);
            DeserializeXmlToAccountList(accountsXml);
        }
    
        // If the UserAccountList does not contain the sampleUser Initialize the sample users
        // This is only needed as it in a Hand on Lab to demonstrate a user migrating
        // In the real world user accounts would just be in a database
        if (!_mockDatabaseUserAccountsList.Any(f = > f.Username.Equals("sampleUsername")))
                {
            //If the list is empty InitializeSampleUserAccountsAsync and return the list
            await InitializeSampleUserAccountsAsync();
        }
    }
    
  • Die Liste der Benutzerkonten im Pseudospeicher kann jetzt gespeichert und geladen werden. Andere Teile der Anwendung müssen Zugriff auf diese Liste haben, sodass es einige Methoden zum Abrufen dieser Daten geben muss. Fügen Sie unter der InitializeSampleUserAccountsAsync-Methode die folgenden Methoden zum Abrufen von Daten hinzu. Sie ermöglichen es Ihnen, eine Benutzer-ID, einen einzelnen Benutzer, eine Liste der Benutzer für ein bestimmtes Windows Hello-Gerät und auch den öffentlichen Schlüssel für den Benutzer auf einem bestimmten Gerät abzurufen.

    public Guid GetUserId(string username)
    {
        if (_mockDatabaseUserAccountsList.Any())
        {
            UserAccount account = _mockDatabaseUserAccountsList.FirstOrDefault(f => f.Username.Equals(username));
            if (account != null)
            {
                return account.UserId;
            }
        }
        return Guid.Empty;
    }
    
    public UserAccount GetUserAccount(Guid userId)
    {
        return _mockDatabaseUserAccountsList.FirstOrDefault(f => f.UserId.Equals(userId));
    }
    
    public List<UserAccount> GetUserAccountsForDevice(Guid deviceId)
    {
        var usersForDevice = new List<UserAccount>();
    
        foreach (UserAccount account in _mockDatabaseUserAccountsList)
        {
            if (account.WindowsHelloDevices.Any(f => f.DeviceId.Equals(deviceId)))
            {
                usersForDevice.Add(account);
            }
        }
    
        return usersForDevice;
    }
    
    public byte[] GetPublicKey(Guid userId, Guid deviceId)
    {
        UserAccount account = _mockDatabaseUserAccountsList.FirstOrDefault(f => f.UserId.Equals(userId));
        if (account != null)
        {
            if (account.WindowsHelloDevices.Any())
            {
                return account.WindowsHelloDevices.FirstOrDefault(p => p.DeviceId.Equals(deviceId)).PublicKey;
            }
        }
        return null;
    }
    
  • Die nächsten zu implementierenden Methoden behandeln einfache Vorgänge zum Hinzufügen eines Kontos, Entfernen eines Kontos und zum Entfernen eines Geräts. Das Entfernen eines Geräts ist erforderlich, da Windows Hello gerätespezifisch ist. Für jedes Gerät, bei dem Sie sich anmelden, wird ein neues paar öffentlicher und privater Schlüssel von Windows Hello erstellt. Es ist so, als ob Sie ein anderes Kennwort für jedes Gerät haben, bei dem Sie sich anmelden. Das einzige, was Sie nicht benötigen, um sich alle diese Kennwörter zu merken; Der Server führt dies aus. Fügen Sie die folgenden Methoden zum MockStore.cs hinzu.

    public async Task<UserAccount> AddAccountAsync(string username)
    {
        UserAccount newAccount = null;
        try
        {
            newAccount = new UserAccount()
            {
                UserId = Guid.NewGuid(),
                Username = username,
            };
    
            _mockDatabaseUserAccountsList.Add(newAccount);
            await SaveAccountListAsync();
        }
        catch (Exception)
        {
            throw;
        }
        return newAccount;
    }
    
    public async Task<bool> RemoveAccountAsync(Guid userId)
    {
        UserAccount userAccount = GetUserAccount(userId);
        if (userAccount != null)
        {
            _mockDatabaseUserAccountsList.Remove(userAccount);
            await SaveAccountListAsync();
            return true;
        }
        return false;
    }
    
    public async Task<bool> RemoveDeviceAsync(Guid userId, Guid deviceId)
    {
        UserAccount userAccount = GetUserAccount(userId);
        WindowsHelloDevice deviceToRemove = null;
        if (userAccount != null)
        {
            foreach (WindowsHelloDevice device in userAccount.WindowsHelloDevices)
            {
                if (device.DeviceId.Equals(deviceId))
                {
                    deviceToRemove = device;
                    break;
                }
            }
        }
    
        if (deviceToRemove != null)
        {
            //Remove the WindowsHelloDevice
            userAccount.WindowsHelloDevices.Remove(deviceToRemove);
            await SaveAccountListAsync();
        }
    
        return true;
    }
    
  • Fügen Sie in der MockStore-Klasse eine Methode hinzu, die Windows Hello-bezogene Informationen zu einem vorhandenen Benutzerkonto hinzufügt. Diese Methode wird als "WindowsHelloUpdateDetailsAsync" bezeichnet und verwendet Parameter, um den Benutzer zu identifizieren, und die Windows Hello-Details. Das KeyAttestationResult wurde beim Erstellen einer WindowsHelloDevice in einer realen Anwendung auskommentiert, die Sie benötigen würden.

    using System.Threading.Tasks;
    using Windows.Security.Credentials;
    
    public async Task WindowsHelloUpdateDetailsAsync(Guid userId, Guid deviceId, byte[] publicKey, 
        KeyCredentialAttestationResult keyAttestationResult)
    {
        UserAccount existingUserAccount = GetUserAccount(userId);
        if (existingUserAccount != null)
        {
            if (!existingUserAccount.WindowsHelloDevices.Any(f => f.DeviceId.Equals(deviceId)))
            {
                existingUserAccount.WindowsHelloDevices.Add(new WindowsHelloDevice()
                {
                    DeviceId = deviceId,
                    PublicKey = publicKey,
                    // KeyAttestationResult = keyAttestationResult
                });
            }
        }
        await SaveAccountListAsync();
    }
    
  • Die MockStore-Klasse ist jetzt abgeschlossen, da dies die Datenbank darstellt, die als privat betrachtet werden sollte. Um auf den MockStore zuzugreifen, ist eine AuthService-Klasse erforderlich, um die Datenbankdaten zu bearbeiten. Erstellen Sie im Ordner "AuthService " eine neue Klasse namens "AuthService.cs". Ändern Sie den Klassenbereich in "Öffentlich", und fügen Sie ein Singleton-Instanzmuster hinzu, um sicherzustellen, dass immer nur eine Instanz erstellt wird.

    namespace WindowsHelloLogin.AuthService
    {
        public class AuthService
        {
            // Singleton instance of the AuthService
            // The AuthService is a mock of what a real world server and service implementation would be
            private static AuthService _instance;
            public static AuthService Instance
            {
                get
                {
                    if (null == _instance)
                    {
                        _instance = new AuthService();
                    }
                    return _instance;
                }
            }
    
            private AuthService()
            { }
        }
    }
    
  • Die AuthService-Klasse muss eine Instanz der MockStore-Klasse erstellen und Zugriff auf die Eigenschaften des MockStore-Objekts bieten.

    namespace WindowsHelloLogin.AuthService
    {
        public class AuthService
        {
            //Singleton instance of the AuthService
            //The AuthService is a mock of what a real world server and database implementation would be
            private static AuthService _instance;
            public static AuthService Instance
            {
                get
                {
                    if (null == _instance)
                    {
                        _instance = new AuthService();
                    }
                    return _instance;
                }
            }
    
            private AuthService()
            { }
    
            private MockStore _mockStore = new();
    
            public Guid GetUserId(string username)
            {
                return _mockStore.GetUserId(username);
            }
    
            public UserAccount GetUserAccount(Guid userId)
            {
                return _mockStore.GetUserAccount(userId);
            }
    
            public List<UserAccount> GetUserAccountsForDevice(Guid deviceId)
            {
                return _mockStore.GetUserAccountsForDevice(deviceId);
            }
        }
    }
    
  • Sie benötigen Methoden in der AuthService-Klasse , um auf Methoden zum Hinzufügen, Entfernen und Aktualisieren von Windows Hello-Details im MockStore-Objekt zuzugreifen. Fügen Sie am Ende der AuthService-Klassendefinition die folgenden Methoden hinzu.

    using System.Threading.Tasks;
    using Windows.Security.Credentials;
    
    public async Task RegisterAsync(string username)
    {
        await _mockStore.AddAccountAsync(username);
    }
    
    public async Task<bool> WindowsHelloRemoveUserAsync(Guid userId)
    {
        return await _mockStore.RemoveAccountAsync(userId);
    }
    
    public async Task<bool> WindowsHelloRemoveDeviceAsync(Guid userId, Guid deviceId)
    {
        return await _mockStore.RemoveDeviceAsync(userId, deviceId);
    }
    
    public async Task WindowsHelloUpdateDetailsAsync(Guid userId, Guid deviceId, byte[] publicKey, 
        KeyCredentialAttestationResult keyAttestationResult)
    {
        await _mockStore.WindowsHelloUpdateDetailsAsync(userId, deviceId, publicKey, keyAttestationResult);
    }
    
  • Die AuthService-Klasse muss eine Methode zum Überprüfen von Anmeldeinformationen bereitstellen. Diese Methode verwendet einen Benutzernamen und ein Kennwort und stellt sicher, dass das Konto vorhanden ist und das Kennwort gültig ist. Ein vorhandenes System hätte eine entsprechende Methode, die überprüft, ob der Benutzer autorisiert ist. Fügen Sie der datei AuthService.cs die folgende ValidateCredentials-Methode hinzu.

    public bool ValidateCredentials(string username, string password)
    {
        if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password))
        {
            // This would be used for existing accounts migrating to use Windows Hello
            Guid userId = GetUserId(username);
            if (userId != Guid.Empty)
            {
                UserAccount account = GetUserAccount(userId);
                if (account != null)
                {
                    if (string.Equals(password, account.Password))
                    {
                        return true;
                    }
                }
            }
        }
        return false;
    }
    
  • Die AuthService-Klasse benötigt eine Anforderungsabfragemethode, die eine Abfrage an den Client zurückgibt, um zu überprüfen, ob der Benutzer der Benutzer sein möchte. Anschließend wird eine weitere Methode in der AuthService-Klasse benötigt, um die signierte Abfrage vom Client zurück zu erhalten. Für diese praktische Übung wurde die Methode, wie Sie bestimmen, ob die signierte Abfrage abgeschlossen wurde, unvollständig gelassen. Jede Implementierung von Windows Hello in ein vorhandenes Authentifizierungssystem unterscheidet sich geringfügig. Der auf dem Server gespeicherte öffentliche Schlüssel muss mit dem Ergebnis übereinstimmen, das der Client an den Server zurückgegeben hat. Fügen Sie diese beiden Methoden zum AuthService.cs hinzu.

    using Windows.Security.Cryptography;
    using Windows.Storage.Streams;
    
    public IBuffer WindowsHelloRequestChallenge()
    {
        return CryptographicBuffer.ConvertStringToBinary("ServerChallenge", BinaryStringEncoding.Utf8);
    }
    
    public bool SendServerSignedChallenge(Guid userId, Guid deviceId, byte[] signedChallenge)
    {
        // Depending on your company polices and procedures this step will be different
        // It is at this point you will need to validate the signedChallenge that is sent back from the client.
        // Validation is used to ensure the correct user is trying to access this account. 
        // The validation process will use the signedChallenge and the stored PublicKey 
        // for the username and the specific device signin is called from.
        // Based on the validation result you will return a bool value to allow access to continue or to block the account.
    
        // For this sample validation will not happen as a best practice solution does not apply and will need to 
           // be configured for each company.
        // Simply just return true.
    
        // You could get the User's Public Key with something similar to the following:
        byte[] userPublicKey = _mockStore.GetPublicKey(userId, deviceId);
        return true;
    }
    

Übung 2: Clientseitige Logik

In dieser Übung ändern Sie die clientseitigen Ansichten und Hilfsklassen aus der ersten Übung, um die AuthService-Klasse zu verwenden. In der realen Welt wäre AuthService der Authentifizierungsserver, und Sie müssten Web-APIs verwenden, um Daten vom Server zu senden und zu empfangen. Für diese praktische Übung sind der Client und der Server lokal, um die Dinge einfach zu halten. Ziel ist es, zu erfahren, wie Sie die Windows Hello-APIs verwenden.

  • Im MainPage.xaml.cs können Sie den AccountHelper.LoadAccountListAsync-Methodenaufruf in der geladenen Methode entfernen, da die AuthService-Klasse eine Instanz des MockStore erstellt, um die Kontenliste zu laden. Die Loaded Methode sollte nun wie der folgende Codeausschnitt aussehen. Beachten Sie, dass die asynchrone Methodendefinition entfernt wird, da nichts erwartet wird.

    private void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        Frame.Navigate(typeof(UserSelection));
    }
    
  • Aktualisieren Sie die Anmeldeseitenschnittstelle , damit ein Kennwort eingegeben werden muss. In dieser praktischen Übung wird veranschaulicht, wie ein vorhandenes System zur Verwendung von Windows Hello migriert werden kann, und vorhandene Konten verfügen über einen Benutzernamen und ein Kennwort. Aktualisieren Sie außerdem die Erläuterung am unteren Rand des XAML-Codes, um das Standardkennwort einzuschließen. Aktualisieren Sie den folgenden XAML-Code in "Login.xaml".

    <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 credentials below" Margin="0,0,0,20"
                   TextWrapping="Wrap" Width="300"
                   TextAlignment="Center" VerticalAlignment="Center" FontSize="16"/>
    
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
          <!-- Username Input -->
          <TextBlock x:Name="UserNameTextBlock" Text="Username: "
                     FontSize="20" Margin="4" Width="100"/>
          <TextBox x:Name="UsernameTextBox" PlaceholderText="sampleUsername" Width="200" Margin="4"/>
        </StackPanel>
    
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
          <!-- Password Input -->
          <TextBlock x:Name="PasswordTextBlock" Text="Password: "
                     FontSize="20" Margin="4" Width="100"/>
          <PasswordBox x:Name="PasswordBox" PlaceholderText="samplePassword" Width="200" Margin="4"/>
        </StackPanel>
    
        <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' and default password 'samplePassword'"/>
      </StackPanel>
    </Grid>
    
  • In der CodeBehind-Datei für die Login-Klasse müssen Sie die Account private Variable oben in der Klasse in eine UserAccountändern. Ändern Sie das OnNavigateTo Ereignis, um den Typ in ein UserAccount. Sie benötigen auch die folgende Using-Anweisung.

    using WindowsHelloLogin.AuthService;
    
    namespace WindowsHelloLogin.Views
    {
        public sealed partial class Login : Page
        {
            private UserAccount _account;
            private bool _isExistingAccount;
    
            public Login()
            {
                this.InitializeComponent();
            }
    
            protected override async void OnNavigatedTo(NavigationEventArgs e)
            {
                //Check Windows Hello is setup 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 = (UserAccount)e.Parameter;
                        UsernameTextBox.Text = _account.Username;
                        await SignInWindowsHelloAsync();
                    }
                }
            }
    
            private async void LoginButton_Click(object sender, RoutedEventArgs e)
            {
                ErrorMessage.Text = "";
                await SignInWindowsHelloAsync();
            }
        }
    }
    
  • Da die Anmeldeseite anstelle des vorherigen Account Objekts ein UserAccount Objekt verwendet, muss die WindowsHelloHelper.cs aktualisiert werden, um einen UserAccount Parametertyp für einige Methoden zu verwenden. Sie müssen die folgenden Parameter für die Methoden CreateWindowsHelloKeyAsync, RemoveWindowsHelloAccountAsync und GetWindowsHelloAuthenticationMessageAsync ändern. Da die UserAccount Klasse über eine Guid UserId verfügt, beginnen Sie mit der Verwendung der ID an weiteren Stellen, um präziser zu sein.

    public static async Task<bool> CreateWindowsHelloKeyAsync(Guid userId, string username)
    {
        KeyCredentialRetrievalResult keyCreationResult = await KeyCredentialManager.RequestCreateAsync(username, KeyCredentialCreationOption.ReplaceExisting);
    
        return true;
    }
    
    public static async void RemoveWindowsHelloAccountAsync(UserAccount account)
    {
    
    }
    public static async Task<bool> GetWindowsHelloAuthenticationMessageAsync(UserAccount 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.UserId, account.Username))
            {
                //If the Windows Hello Key was again successfully created, Windows Hello has just been reset.
                //Now that the Windows 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;
    }
    
  • Die SignInWindowsHelloAsync-Methode in Login.xaml.cs Datei muss aktualisiert werden, um AuthService anstelle von AccountHelper zu verwenden. Die Überprüfung der Anmeldeinformationen erfolgt über AuthService. Für diese praktische Übung lautet das einzige konfigurierte Konto "sampleUsername". Dieses Konto wird in der InitializeSampleUserAccountsAsync-Methode in MockStore.cs erstellt. Aktualisieren Sie die SignInWindowsHelloAsync-Methode in Login.xaml.cs jetzt, um den folgenden Codeausschnitt widerzuspiegeln.

    private async Task SignInWindowsHelloAsync()
    {
        if (_isExistingAccount)
        {
            if (await WindowsHelloHelper.GetWindowsHelloAuthenticationMessageAsync(_account))
            {
                Frame.Navigate(typeof(Welcome), _account);
            }
        }
        else if (AuthService.AuthService.Instance.ValidateCredentials(UsernameTextBox.Text, PasswordBox.Password))
        {
            Guid userId = AuthService.AuthService.Instance.GetUserId(UsernameTextBox.Text);
    
            if (userId != Guid.Empty)
            {
                //Now that the account exists on server try and create the necessary details and add them to the account
                if (await WindowsHelloHelper.CreateWindowsHelloKeyAsync(userId, UsernameTextBox.Text))
                {
                    Debug.WriteLine("Successfully signed in with Windows Hello!");
                    //Navigate to the Welcome Screen. 
                    _account = AuthService.AuthService.Instance.GetUserAccount(userId);
                    Frame.Navigate(typeof(Welcome), _account);
                }
                else
                {
                    //The Windows Hello account creation failed.
                    //Remove the account from the server as the details were not configured
                    await AuthService.AuthService.Instance.WindowsHelloRemoveUserAsync(userId);
    
                    ErrorMessage.Text = "Account Creation Failed";
                }
            }
        }
        else
        {
            ErrorMessage.Text = "Invalid Credentials";
        }
    }
    
  • Da Windows Hello für jedes Konto auf jedem Gerät ein anderes öffentliches und privates Schlüsselpaar erstellt, muss auf der Willkommensseite eine Liste der registrierten Geräte für das angemeldete Konto angezeigt werden, und jeder muss vergessen werden. Fügen Sie in "Welcome.xaml" den folgenden XAML-Code unterhalb der Datei hinzu ForgetButton. Dadurch wird eine Schaltfläche "Gerät vergessen", ein Fehlertextbereich und eine Liste zum Anzeigen aller Geräte implementiert.

    <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"/>
    
        <Button x:Name="ForgetDeviceButton" Content="Forget Device" Click="Button_Forget_Device_Click"
                Foreground="White"
                Background="Gray"
                Margin="0,40,0,20"
                HorizontalAlignment="Center"/>
    
        <TextBlock x:Name="ForgetDeviceErrorTextBlock" Text="Select a device first"
                   TextWrapping="Wrap" Width="300" Foreground="Red"
                   TextAlignment="Center" VerticalAlignment="Center" FontSize="16" Visibility="Collapsed"/>
    
        <ListView x:Name="UserListView" MaxHeight="500" MinWidth="350" Width="350" HorizontalAlignment="Center">
          <ListView.ItemTemplate>
            <DataTemplate>
              <Grid Background="Gray" Height="50" Width="350" HorizontalAlignment="Center" VerticalAlignment="Stretch" >
                <TextBlock Text="{Binding DeviceId}" HorizontalAlignment="Center" TextAlignment="Center" VerticalAlignment="Center"
                           Foreground="White"/>
              </Grid>
            </DataTemplate>
          </ListView.ItemTemplate>
        </ListView>
      </StackPanel>
    </Grid>
    
  • In der datei Welcome.xaml.cs müssen Sie die private Account Variable oben in der Klasse in eine private UserAccount Variable ändern. Aktualisieren Sie dann die OnNavigatedTo Methode, um AuthService zu verwenden und Informationen für das aktuelle Konto abzurufen. Wenn Sie über die Kontoinformationen verfügen, können Sie die ItemsSource Liste so festlegen, dass die Geräte angezeigt werden. Sie müssen einen Verweis auf den AuthService-Namespace hinzufügen.

    using WindowsHelloLogin.AuthService;
    
    namespace WindowsHelloLogin.Views
    {
        public sealed partial class Welcome : Page
        {
            private UserAccount _activeAccount;
    
            public Welcome()
            {
                InitializeComponent();
            }
    
            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                _activeAccount = (UserAccount)e.Parameter;
                if (_activeAccount != null)
                {
                    UserAccount account = AuthService.AuthService.Instance.GetUserAccount(_activeAccount.UserId);
                    if (account != null)
                    {
                        UserListView.ItemsSource = account.WindowsHelloDevices;
                        UserNameText.Text = account.Username;
                    }
                }
            }
        }
    }
    
  • Wenn Sie beim Entfernen eines Kontos den AuthService verwenden, kann der Verweis auf accountHelper in der Button_Forget_User_Click Methode entfernt werden. Die Methode sollte nun wie unten dargestellt aussehen.

    private async void Button_Forget_User_Click(object sender, RoutedEventArgs e)
    {
        //Remove it from Windows Hello
        await WindowsHelloHelper.RemoveWindowsHelloAccountAsync(_activeAccount);
    
        Debug.WriteLine($"User {_activeAccount.Username} deleted.");
    
        //Navigate back to UserSelection page.
        Frame.Navigate(typeof(UserSelection));
    }
    
  • Die WindowsHelloHelper-Methode verwendet AuthService nicht, um das Konto zu entfernen. Sie müssen einen Aufruf an AuthService durchführen und die UserId übergeben.

    public static async void RemoveWindowsHelloAccountAsync(UserAccount 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
            await AuthService.AuthService.Instance.WindowsHelloRemoveUserAsync(account.UserId);
        }
    
        //Then delete the account from the machines list of Windows Hello Accounts
        await KeyCredentialManager.DeleteAsync(account.Username);
    }
    
  • Bevor Sie die Implementierung der Willkommensseite abschließen können, müssen Sie eine Methode in WindowsHelloHelper.cs erstellen, die das Entfernen eines Geräts ermöglicht. Erstellen Sie eine neue Methode, die WindowsHelloRemoveDeviceAsync in AuthService aufruft.

    public static async Task RemoveWindowsHelloDeviceAsync(UserAccount account, Guid deviceId)
    {
        await AuthService.AuthService.Instance.WindowsHelloRemoveDeviceAsync(account.UserId, deviceId);
    }
    
  • Implementieren Sie in Welcome.xaml.cs den Button_Forget_Device_Click Ereignishandler. Dadurch wird das ausgewählte Gerät aus der Liste der Geräte verwendet und das Windows Hello-Hilfsprogramm verwendet, um das Gerät zu entfernen. Denken Sie daran, den Ereignishandler asynchron zu machen.

    private async void Button_Forget_Device_Click(object sender, RoutedEventArgs e)
    {
        WindowsHelloDevice selectedDevice = UserListView.SelectedItem as WindowsHelloDevice;
        if (selectedDevice != null)
        {
            //Remove it from Windows Hello
            await WindowsHelloHelper.RemoveWindowsHelloDeviceAsync(_activeAccount, selectedDevice.DeviceId);
    
            Debug.WriteLine($"User {_activeAccount.Username} deleted.");
    
            if (!UserListView.Items.Any())
            {
                //Navigate back to UserSelection page.
                Frame.Navigate(typeof(UserSelection));
            }
        }
        else
        {
            ForgetDeviceErrorTextBlock.Visibility = Visibility.Visible;
        }
    }
    
  • Die nächste Seite, die Sie aktualisieren, ist die UserSelection-Seite . Die UserSelection-Seite muss AuthService verwenden, um alle Benutzerkonten für das aktuelle Gerät abzurufen. Derzeit gibt es keine Möglichkeit, eine Geräte-ID an den AuthService zu übergeben, sodass Benutzerkonten für dieses Gerät zurückgegeben werden können. Erstellen Sie im Ordner "Utils " eine neue Klasse namens "Helpers.cs". Ändern Sie den Klassenbereich als öffentlich statisch, und fügen Sie dann die folgende Methode hinzu, mit der Sie die aktuelle Geräte-ID abrufen können.

    using System;
    using Windows.Security.ExchangeActiveSyncProvisioning;
    
    namespace WindowsHelloLogin.Utils
    {
        public static class Helpers
        {
            public static Guid GetDeviceId()
            {
                //Get the Device ID to pass to the server
                var deviceInformation = new EasClientDeviceInformation();
                return deviceInformation.Id;
            }
        }
    }
    
  • In der UserSelection-Seitenklasse muss sich nur der CodeBehind ändern, nicht die Benutzeroberfläche. Aktualisieren Sie in UserSelection.xaml.cs die UserSelection_Loaded-Methode und die UserSelectionChanged-Methode , um die UserAccount Klasse anstelle der Account Klasse zu verwenden. Sie müssen auch alle Benutzer für dieses Gerät über AuthService abrufen.

    using System.Linq;
    using WindowsHelloLogin.AuthService;
    
    namespace WindowsHelloLogin.Views
    {
        public sealed partial class UserSelection : Page
        {
            public UserSelection()
            {
                InitializeComponent();
                Loaded += UserSelection_Loaded;
            }
    
            private void UserSelection_Loaded(object sender, RoutedEventArgs e)
            {
                List<UserAccount> accounts = AuthService.AuthService.Instance.GetUserAccountsForDevice(Helpers.GetDeviceId());
    
                if (accounts.Any())
                {
                    UserListView.ItemsSource = accounts;
                    UserListView.SelectionChanged += UserSelectionChanged;
                }
                else
                {
                    //If there are no accounts navigate to the Login page
                    Frame.Navigate(typeof(Login));
                }
            }
    
            /// <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)
                {
                    UserAccount account = (UserAccount)((ListView)sender).SelectedValue;
                    if (account != null)
                    {
                        Debug.WriteLine($"Account {account.Username} selected!");
                    }
                    Frame.Navigate(typeof(Login), account);
                }
            }
        }
    }
    
  • Auf der Seite "WindowsHelloRegister " muss die CodeBehind-Datei aktualisiert werden. Die Benutzeroberfläche benötigt keine Änderungen. Entfernen Sie in WindowsHelloRegister.xaml.cs die private Account Variable oben in der Klasse, da sie nicht mehr benötigt wird. Aktualisieren Sie den RegisterButton_Click_Async Ereignishandler für die Verwendung von AuthService. Diese Methode erstellt ein neues Benutzerkonto , und versuchen Sie dann, die Kontodetails zu aktualisieren. Wenn Windows Hello einen Schlüssel nicht erstellt, wird das Konto entfernt, da der Registrierungsprozess fehlgeschlagen ist.

    private async void RegisterButton_Click_Async(object sender, RoutedEventArgs e)
    {
        ErrorMessage.Text = "";
    
        //Validate entered credentials are acceptable
        if (!string.IsNullOrEmpty(UsernameTextBox.Text))
        {
            //Register an Account on the AuthService so that we can get back a userId
            await AuthService.AuthService.Instance.RegisterAsync(UsernameTextBox.Text);
            Guid userId = AuthService.AuthService.Instance.GetUserId(UsernameTextBox.Text);
    
            if (userId != Guid.Empty)
            {
                //Now that the account exists on server try and create the necessary details and add them to the account
                if (await WindowsHelloHelper.CreateWindowsHelloKeyAsync(userId, UsernameTextBox.Text))
                {
                    //Navigate to the Welcome Screen. 
                    Frame.Navigate(typeof(Welcome), AuthService.AuthService.Instance.GetUserAccount(userId));
                }
                else
                {
                    //The Windows Hello account creation failed.
                    //Remove the account from the server as the details were not configured
                    await AuthService.AuthService.Instance.WindowsHelloRemoveUserAsync(userId);
    
                    ErrorMessage.Text = "Account Creation Failed";
                }
            }
        }
        else
        {
            ErrorMessage.Text = "Please enter a username";
        }
    }
    
  • Erstellen Sie die Anwendung, und führen Sie sie aus. Melden Sie sich mit den Anmeldeinformationen "sampleUsername" und "samplePassword" beim Beispielbenutzerkonto an. Auf der Willkommensseite wird möglicherweise die Schaltfläche "Geräte vergessen" angezeigt, es gibt jedoch keine Geräte. Wenn Sie einen Benutzer zum Arbeiten mit Windows Hello erstellen oder migrieren, werden die Kontoinformationen nicht an AuthService übertragen.

    Screenshot des Windows Hello-Anmeldebildschirms

    Screenshot der erfolgreich abgeschlossenen Windows Hello-Anmeldung

  • Um die Windows Hello-Kontoinformationen zum AuthService abzurufen, müssen die WindowsHelloHelper.cs aktualisiert werden. In der CreateWindowsHelloKeyAsync-Methode müssen Sie eine neue Methode aufrufen, true die versucht, die KeyAttestation abzurufen. Während diese praktische Übung diese Informationen im AuthService nicht aufzeichnet, erfahren Sie, wie Sie diese Informationen auf der Clientseite abrufen würden. Aktualisieren Sie die CreateWindowsHelloKeyAsync-Methode wie folgt:

    public static async Task<bool> CreateWindowsHelloKeyAsync(Guid userId, string username)
    {
        KeyCredentialRetrievalResult keyCreationResult = await KeyCredentialManager.RequestCreateAsync(username, KeyCredentialCreationOption.ReplaceExisting);
    
        switch (keyCreationResult.Status)
        {
            case KeyCredentialStatus.Success:
                Debug.WriteLine("Successfully made key");
                await GetKeyAttestationAsync(userId, keyCreationResult);
                return true;
            case KeyCredentialStatus.UserCanceled:
                Debug.WriteLine("User cancelled sign-in process.");
                break;
            case KeyCredentialStatus.NotFound:
                // User needs to setup Windows Hello
                Debug.WriteLine($"Windows Hello is not set up!{Environment.NewLine}Please go to Windows Settings and set up a PIN to use it.");
                break;
            default:
                break;
        }
    
        return false;
    }
    
  • Erstellen Sie eine GetKeyAttestationAsync-Methode in WindowsHelloHelper.cs. Diese Methode veranschaulicht, wie Sie alle erforderlichen Informationen abrufen, die von Windows Hello für jedes Konto auf einem bestimmten Gerät bereitgestellt werden können.

    using Windows.Storage.Streams;
    
    private static async Task GetKeyAttestationAsync(Guid userId, KeyCredentialRetrievalResult keyCreationResult)
    {
        KeyCredential userKey = keyCreationResult.Credential;
        IBuffer publicKey = userKey.RetrievePublicKey();
        KeyCredentialAttestationResult keyAttestationResult = await userKey.GetAttestationAsync();
        IBuffer keyAttestation = null;
        IBuffer certificateChain = null;
        bool keyAttestationIncluded = false;
        bool keyAttestationCanBeRetrievedLater = false;
        KeyCredentialAttestationStatus keyAttestationRetryType = 0;
    
        if (keyAttestationResult.Status == KeyCredentialAttestationStatus.Success)
        {
            keyAttestationIncluded = true;
            keyAttestation = keyAttestationResult.AttestationBuffer;
            certificateChain = keyAttestationResult.CertificateChainBuffer;
            Debug.WriteLine("Successfully made key and attestation");
        }
        else if (keyAttestationResult.Status == KeyCredentialAttestationStatus.TemporaryFailure)
        {
            keyAttestationRetryType = KeyCredentialAttestationStatus.TemporaryFailure;
            keyAttestationCanBeRetrievedLater = true;
            Debug.WriteLine("Successfully made key but not attestation");
        }
        else if (keyAttestationResult.Status == KeyCredentialAttestationStatus.NotSupported)
        {
            keyAttestationRetryType = KeyCredentialAttestationStatus.NotSupported;
            keyAttestationCanBeRetrievedLater = false;
            Debug.WriteLine("Key created, but key attestation not supported");
        }
    
        Guid deviceId = Helpers.GetDeviceId();
    
        //Update the Windows Hello details with the information we have just fetched above.
        //await UpdateWindowsHelloDetailsAsync(userId, deviceId, publicKey.ToArray(), keyAttestationResult);
    }
    
  • Möglicherweise haben Sie in der GetKeyAttestationAsync-Methode bemerkt, dass Sie gerade die letzte Zeile auskommentiert haben. Diese letzte Zeile ist eine neue Methode, die Sie erstellen, die alle Windows Hello-Informationen an AuthService sendet. In der realen Welt müssten Sie dies über eine Web-API an einen tatsächlichen Server senden.

    using System.Runtime.InteropServices.WindowsRuntime;
    using System.Threading.Tasks;
    
    public static async Task<bool> UpdateWindowsHelloDetailsAsync(Guid userId, Guid deviceId, byte[] publicKey, KeyCredentialAttestationResult keyAttestationResult)
    {
        //In the real world, you would use an API to add Windows Hello signing info to server for the signed in account.
        //For this tutorial, we do not implement a Web API for our server and simply mock the server locally.
        //The CreateWindowsHelloKey method handles adding the Windows Hello account locally to the device using the KeyCredential Manager
    
        //Using the userId the existing account should be found and updated.
        await AuthService.AuthService.Instance.WindowsHelloUpdateDetailsAsync(userId, deviceId, publicKey, keyAttestationResult);
        return true;
    }
    
  • Heben Sie die Kommentare der letzten Zeile in der GetKeyAttestationAsync-Methode auf, sodass die Windows Hello-Informationen an den AuthService gesendet werden.

  • Erstellen Sie die Anwendung, und führen Sie sie aus, und melden Sie sich wie zuvor mit den Standardanmeldeinformationen an. Auf der Willkommensseite sehen Sie nun, dass die Geräte-ID angezeigt wird. Wenn Sie sich auf einem anderen Gerät angemeldet haben, das auch hier angezeigt wird (wenn Sie einen in der Cloud gehosteten Authentifizierungsdienst hatten). Für diese praktische Übung wird die tatsächliche Geräte-ID angezeigt. In einer echten Implementierung möchten Sie einen Anzeigenamen anzeigen, den eine Person verstehen und verwenden kann, um jedes Gerät zu identifizieren.

    Screenshot der erfolgreichen Anmeldung von Windows Hello mit der Geräte-ID

  • Um diese praktische Übung abzuschließen, benötigen Sie eine Anforderung und Herausforderung für den Benutzer, wenn er auf der Benutzerauswahlseite ausgewählt und wieder angemeldet ist. Der AuthService verfügt über zwei Methoden, die Sie erstellt haben, um eine Abfrage anzufordern, eine, die eine signierte Abfrage verwendet. Erstellen Sie in WindowsHelloHelper.cs eine neue Methode namens "RequestSignAsync". Dadurch wird eine Abfrage vom AuthService angefordert, lokal signiert, dass diese Abfrage mit einer Windows Hello-API verwendet wird, und die signierte Abfrage an AuthService senden. In dieser praktischen Übung erhält AuthService die signierte Abfrage und gibt zurück true. In einer tatsächlichen Implementierung müssen Sie einen Überprüfungsmechanismus implementieren, um festzustellen, ob die Abfrage vom richtigen Benutzer auf dem richtigen Gerät signiert wurde. Fügen Sie der WindowsHelloHelper.cs die folgende Methode hinzu.

    private static async Task<bool> RequestSignAsync(Guid userId, KeyCredentialRetrievalResult openKeyResult)
    {
        // Calling userKey.RequestSignAsync() prompts the uses to enter the PIN or use Biometrics (Windows Hello).
        // The app would use the private key from the user account to sign the sign-in request (challenge)
        // The client would then send it back to the server and await the servers response.
        IBuffer challengeMessage = AuthService.AuthService.Instance.WindowsHelloRequestChallenge();
        KeyCredential userKey = openKeyResult.Credential;
        KeyCredentialOperationResult signResult = await userKey.RequestSignAsync(challengeMessage);
    
        if (signResult.Status == KeyCredentialStatus.Success)
        {
            // If the challenge from the server is signed successfully
            // send the signed challenge back to the server and await the servers response
            return AuthService.AuthService.Instance.SendServerSignedChallenge(
                userId, Helpers.GetDeviceId(), signResult.Result.ToArray());
        }
        else if (signResult.Status == KeyCredentialStatus.UserCanceled)
        {
            // User cancelled the Windows Hello PIN entry.
        }
        else if (signResult.Status == KeyCredentialStatus.NotFound)
        {
            // Must recreate Windows Hello key
        }
        else if (signResult.Status == KeyCredentialStatus.SecurityDeviceLocked)
        {
            // Can't use Windows Hello right now, remember that hardware failed and suggest restart
        }
        else if (signResult.Status == KeyCredentialStatus.UnknownError)
        {
            // Can't use Windows Hello right now, try again later
        }
    
        return false;
    }
    
  • Rufen Sie in der WindowsHelloHelper-Klasse die RequestSignAsync-Methode aus der GetWindowsHelloAuthenticationMessageAsync-Methode auf.

    public static async Task<bool> GetWindowsHelloAuthenticationMessageAsync(UserAccount 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.
    
            return await RequestSignAsync(account.UserId, openKeyResult);
        }
        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.UserId, account.Username))
            {
                //If the Windows Hello Key was again successfully created, Windows Hello has just been reset.
                //Now that the Windows 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;
    }
    
  • In dieser Übung haben Sie die clientseitige Anwendung so aktualisiert, dass sie AuthService verwendet. Dadurch konnten Sie die Notwendigkeit der Account-Klasse und der AccountHelper-Klasse beseitigen. Löschen Sie die Account-Klasse , den Ordner "Models " und die Klasse "AccountHelper " im Ordner "Utils ". Sie müssen alle Verweise auf den WindowsHelloLogin.Models Namespace in der gesamten Anwendung entfernen, bevor die Lösung erfolgreich erstellt wird.

  • Erstellen Sie die Anwendung, und führen Sie sie aus, und verwenden Sie Windows Hello mit dem Simulierten Dienst und der Datenbank.

In dieser praktischen Übung haben Sie erfahren, wie Sie die Windows Hello-APIs verwenden, um die Notwendigkeit von Kennwörtern bei der Verwendung der Authentifizierung von einem Windows-Computer zu ersetzen. Wenn Sie überlegen, wie viel Energie von Personen verbraucht wird, die Kennwörter verwalten und verlorene Kennwörter in vorhandenen Systemen unterstützen, sollten Sie den Vorteil des Wechsels zu diesem neuen Windows Hello-Authentifizierungssystem sehen.

Wir haben uns als Übung für Sie die Details dazu verlassen, wie Sie die Authentifizierung auf dienst- und serverseitiger Seite implementieren. Es wird erwartet, dass die meisten Entwickler über vorhandene Systeme verfügen, die migriert werden müssen, um mit der Arbeit mit Windows Hello zu beginnen. Die Details der einzelnen Systeme unterscheiden sich.