Text="Please Note: To demonstrate a login, validation will only occur using the default username 'sampleUsername'"/>
需要向后台代码文件添加一些方法以实现解决方案构建。 按 F7 或使用 解决方案资源管理器 编辑Login.xaml.cs文件。 添加以下两个事件方法来处理 登录 和 注册 事件。 目前,这些方法将把 ErrorMessage.Text 设置为空字符串。 请务必包含以下 using 语句。 后续步骤将需要它们。
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 = "";
}
}
}
若要呈现登录页,请编辑 MainPage 代码,以便在加载 MainPage 时导航到登录页。 打开MainPage.xaml.cs文件。 在 解决方案资源管理器中,双击MainPage.xaml.cs。 如果找不到此项,请单击 MainPage.xaml 旁边的小箭头以显示代码隐藏文件。
创建加载事件处理程序方法,该方法将跳转到登录页。
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));
}
}
}
在 “登录 ”页中,需要处理事件 OnNavigatedTo ,以验证当前计算机上是否提供 Windows Hello。 在Login.xaml.cs中,实现以下代码。 你会注意到 WindowsHelloHelper 对象指示存在错误。 这是因为我们尚未创建此辅助类。
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;
}
}
}
若要创建 WindowsHelloHelper 类,请右键单击 WindowsHelloLogin 项目,然后单击“ 添加新>文件夹”。 将此文件夹命名 为 Utils。
右键单击 Utils 文件夹,然后选择 “添加>类”。 将此新类命名为“WindowsHelloHelper.cs”。
将 WindowsHelloHelper 类的范围更改为 public static,然后添加以下方法,以通知用户是否准备好使用 Windows Hello。 需要添加所需的命名空间。
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Windows.Security.Credentials;
namespace WindowsHelloLogin.Utils
{
public static class WindowsHelloHelper
{
///
/// 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
///
public static async Task 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;
}
}
}
在Login.xaml.cs中,添加对命名空间的 WindowsHelloLogin.Utils 引用。 这将解决方法中的 OnNavigatedTo 错误。
using WindowsHelloLogin.Utils;
生成并运行应用程序。 你将导航到登录页,Windows Hello 横幅将指示你是否准备好使用 Windows Hello。 应会看到指示计算机上的 Windows Hello 状态的绿色或蓝色横幅。
接下来需要做的是生成用于登录的逻辑。 在名为“Models”的项目中创建一个新文件夹。
在 Models 文件夹中,创建名为“Account.cs”的新类。 此类将作为您的帐户模型。 由于这是一个示例项目,它只包含用户名。 将类范围更改为 public 并添加 Username 属性。
namespace WindowsHelloLogin.Models
{
public class Account
{
public string Username { get; set; }
}
}
应用需要一种方法来处理帐户。 对于此动手实验室,由于没有服务器或数据库,因此会在本地保存并加载用户列表。 右键单击 Utils 文件夹并添加名为“AccountHelper.cs”的新类。 将类范围更改为 public static.
AccountHelper 是一个静态类,其中包含在本地保存和加载帐户列表所需的所有方法。 使用 XmlSerializer 实现保存和加载功能。 还需要记住已保存的文件及其保存位置。
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 AccountList = [];
///
/// Create and save a useraccount list file. (Updating the old one)
///
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);
}
}
///
/// Gets the useraccount list file and deserializes it from XML to a list of useraccount objects.
///
/// List of useraccount objects
public static async Task> LoadAccountListAsync()
{
if (File.Exists(_accountListPath))
{
StorageFile accountsFile = await StorageFile.GetFileFromPathAsync(_accountListPath);
string accountsXml = await FileIO.ReadTextAsync(accountsFile);
DeserializeXmlToAccountList(accountsXml);
}
return AccountList;
}
///
/// Uses the local list of accounts and returns an XML formatted string representing the list
///
/// XML formatted list of accounts
public static string SerializeAccountListToXml()
{
var xmlizer = new XmlSerializer(typeof(List));
var writer = new StringWriter();
xmlizer.Serialize(writer, AccountList);
return writer.ToString();
}
///
/// Takes an XML formatted string representing a list of accounts and returns a list object of accounts
///
/// XML formatted list of accounts
/// List object of accounts
public static List DeserializeXmlToAccountList(string listAsXml)
{
var xmlizer = new XmlSerializer(typeof(List));
TextReader textreader = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(listAsXml)));
return AccountList = (xmlizer.Deserialize(textreader)) as List;
}
}
}
接下来,实现从本地帐户列表中添加和删除帐户的方法。 这些操作将保存列表。 此动手实验需要的最后一个方法是验证方法。 由于没有授权服务器或用户的数据库,这将针对硬编码的单个用户进行验证。 这些方法应添加到 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;
}
接下来需要做的是处理来自用户的登录请求。 在Login.xaml.cs中,创建一个新的私有变量,用于保存当前帐户登录。 然后添加名为 SignInWindowsHelloAsync 的新方法。 这将使用 AccountHelper.ValidateAccountCredentials 方法验证帐户凭据。 如果输入的用户名与上一步中配置的硬编码字符串值相同,此方法将返回 布尔 值。 此示例的硬编码值为“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";
}
}
}
}
你可能已注意到在 WindowsHelloHelper 中引用方法的注释代码。 在WindowsHelloHelper.cs中,添加名为 CreateWindowsHelloKeyAsync 的新方法。 此方法在 KeyCredentialManager 中使用 Windows Hello API。 调用 RequestCreateAsync 将创建特定于 accountId 和本地计算机的 Windows Hello 密钥。 如果有兴趣在实际场景中实现,请注意 switch 语句中的注释。
///
/// Creates a Windows Hello key on the machine using the account ID provided.
///
/// The account ID associated with the account that we are enrolling into Windows Hello
/// Boolean indicating if creating the Windows Hello key succeeded
public static async Task 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;
}
创建 CreateWindowsHelloKeyAsync 方法后,返回到Login.xaml.cs文件并取消注释 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";
}
}
生成并运行应用程序。 你将进入“登录”页。 输入用户名“sampleUsername”,然后单击登录。 系统会显示 Windows Hello 提示符,要求你输入 PIN。 正确输入 PIN 后, CreateWindowsHelloKeyAsync 方法将能够创建 Windows Hello 密钥。 监视输出窗口,查看指示成功的消息是否显示。