使用通用 Windows 平台设置 PlayFab 身份验证
本教程指导您完成使用通用 Windows 平台 (UWP) 进行 PlayFab 身份验证的过程。
重要
此过程是对如何通过 Windows Hello 和 PlayFab 获取所有实体和提交身份验证的非常基本的介绍。 有关 Windows Hello 和 PlayFab 身份验证的更复杂示例,请考虑 [UWPExample project](https://github.com/PlayFab/UWPExample)
。
要求
- 按照 MSDN“设置”指南准备 Windows 和 Visual Studio 进行 UWP 开发。
- 拥有已注册的 PlayFab 游戏。
- 熟悉登录基础知识和最佳实践。
注意
以下要求至关重要:使用 Windows 10 操作系统、使用经过验证的 Microsoft 帐户登录、配置好 PIN 等访问接口。 如果不 满足上述要求,应用程序将失败,并且不提供任何有用的原因说明。
准备 Visual Studio 项目
启动 Visual Studio 并创建新项目。
- 在模板下,选择 Windows 通用。
- 然后选择空白应用(通用 Windows) 类型。
- 将其命名为 GettingStartedPlayfabUWP。
- 选择确定按钮提交。
- 选择与您的项目匹配的目标版本和最低版本。
- 选择“确定”按钮。
创建项目后,使用 NuGet 包管理器添加 PlayFab SDK。
- 首先,选择工具选项卡。
- 在下拉菜单中,选择 NuGet 包管理器。
- 然后选择管理解决方案的 NuGet 程序包。
在 NuGet 管理器窗口中:
- 选择浏览并搜索 PlayFabAllSDK 包。
- 选择目标项目。
- 然后选择安装按钮。
完成后,您的基本项目设置即告完成。 在下一节中,将修改应在项目创建时自动生成的 2 个类:
- 应用
- MainPage
实现
App.xaml.cs
此类将通过设置正确的作品 ID 来设置我们的 PlayFab SDK。 不要忘了将作品 ID 替换为您的作品 ID。
using System;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace GettingStartedPlayfabUWP
{
// This class is generated upon project creation.
// While template on it's own contains a lot of xml comments, we are only interested in lines 17 and 27
sealed partial class App : Application
{
// Replace PLAYFAB_TITLE_ID with your own
public const string PlayfabTitleId = "PLAYFAB_TITLE_ID";
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
// This is the only line of functional code we need to add to this class.
PlayFab.PlayFabSettings.TitleId = PlayfabTitleId;
this.InitializeComponent();
this.Suspending += OnSuspending;
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
/// </summary>
/// <param name="e">Details about the launch request and process.</param>
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
//TODO: Load state from previously suspended application
}
// Place the frame in the current Window
Window.Current.Content = rootFrame;
}
if (e.PrelaunchActivated == false)
{
if (rootFrame.Content == null)
{
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}
// Ensure the current window is active
Window.Current.Activate();
}
}
/// <summary>
/// Invoked when Navigation to a certain page fails
/// </summary>
/// <param name="sender">The Frame which failed navigation</param>
/// <param name="e">Details about the navigation failure</param>
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
/// <summary>
/// Invoked when application execution is being suspended. Application state is saved
/// without knowing whether the application will be terminated or resumed with the contents
/// of memory still intact.
/// </summary>
/// <param name="sender">The source of the suspend request.</param>
/// <param name="e">Details about the suspend request.</param>
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
//TODO: Save application state and stop any background activity
deferral.Complete();
}
}
}
MainPage.xaml
此文件包含主页面的布局。 这是一个极其简单 的布局 - 在一个垂直方向的网格中组合了 2 个按钮和一个文本输入控件。
按钮绑定到特定方法,文本框可通过其名称 UsernameInput
访问。
<Page
x:Class="GettingStartedPlayfabUWP.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:GettingStartedPlayfabUWP"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Margin="0,10,10,0">
<TextBox x:Name="UsernameInput" TextWrapping="Wrap" Text="" PlaceholderText="Username..."/>
<Button x:Name="RegisterButton" Content="Register With Hello" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Click="RegisterRequest"/>
<Button x:Name="LoginButton" Content="Sign In With Hello" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Click="LogInRequest"/>
</StackPanel>
</Grid>
</Page>
MainPage.xaml.cs
这是主页面的功能类,它是本示例的核心。 请参阅代码注释,并查看旨在引导您完成 PlayFab + Hello 注册和登录过程的不同方法。
开始学习此代码的最简单方法是查看相应按钮触发的方法:
- RegisterRequest
- LogInRequest。
using System;
using System.Linq;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Networking.Connectivity;
using Windows.Security.Credentials;
using Windows.Security.Cryptography;
using Windows.Security.Cryptography.Core;
using Windows.Storage.Streams;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using PlayFab;
using PlayFab.ClientModels;
namespace GettingStartedPlayfabUWP
{
public sealed partial class MainPage : Page
{
// Shortcut to get current value of UsernameInput
public string Username => UsernameInput.Text;
public MainPage()
{
this.InitializeComponent();
}
/// <summary>
/// This method is invoked when you select the Register button
/// This method illustrates the flow for Registration process.
/// We operate on 2 entities:
/// - User Credentials of type KeyCredential
/// - Public Key of type String
/// We first check if user with this id already has Credentials. If so, we redirect to login procedure.
/// Then we create new User Credentials. Check CreateKeyCredential for implementation details
/// Then we get Base64 encoded Public Key using the new User Credentials. Check GetPublicKeyBase64 for implementation details
/// Then we execute RegisterWithHello api call call. Check CallPlayFabRegisterWithHello for implementation details
/// </summary>
private async void RegisterRequest(object sender, RoutedEventArgs e)
{
// Check if the user already exists and if so log them in.
KeyCredentialRetrievalResult retrieveResult = await KeyCredentialManager.OpenAsync(Username);
if (retrieveResult.Status == KeyCredentialStatus.Success)
{
// Redirect to login procedure
LogInRequest(sender, e);
return;
}
// Create a new KeyCredential for the user on the device.
var credential = await CreateKeyCredential(Username);
if (credential == null) return;
var publicKey = await GetPublicKeyBase64(credential);
if (string.IsNullOrEmpty(publicKey)) return;
// Include the name of the current device for the benefit of the user.
// The server could support a Web interface that shows the user all the devices they
// have signed in from and revoke access from devices they have lost.
var registerResponse = await CallPlayFabRegisterWithHello(publicKey, Username);
await ShowMessage("Registered and signed in with Session Ticket " + registerResponse.Result.SessionTicket);
}
//
/// <summary>
/// This method is invoked when you select the Log In button
/// This method shows entities flow during the sign in process.
/// We have 4 different entities:
/// - User Credentials of type KeyCredential
/// - Public Key Hint of type String
/// - Challenge of type String
/// - SignedChallenge of type String
///
/// We first acquire the User Credentials. We do it based on Username. Check GetUserCredentials method for implementation details
/// Next, we get Public Key Hint based on those credentials. Check GetPublicKeyHint for implementation details.
/// Next we request a Challenge from PlayFab. Check GetPlayFabHelloChallenge for implementation details
/// Next we sign the Challenge using User Credentials, so we obtain Signed Challenge. Check GetPlayFabHelloChallenge for implementation details
/// Finally we use Signed Challenge and Public Key Hint to log into PlayFab. Check CallPlayFabLoginWithHello for implementation details
/// </summary>
private async void LogInRequest(object sender, RoutedEventArgs e)
{
// Get credentials based on current Username.
var credentials = await GetUserCredentials(Username);
if (credentials == null) return;
// Credentials will give us Public Key. We use it to construct Public Key Hint, which is first important entity for PlayFab+UWP authentication.
var publicKeyHint = GetPublicKeyHintBase64(credentials);
if (string.IsNullOrEmpty(publicKeyHint)) return;
// Get PlayFab Challenge to sign for Windows Hello.
var challenge = await GetPlayFabHelloChallenge(publicKeyHint);
if (string.IsNullOrEmpty(challenge)) return;
// Request user to sign the challenge.
var signedChallenge = await RequestUserSignChallenge(credentials, challenge);
if (string.IsNullOrEmpty(signedChallenge)) return;
// Send the signature back to the server to confirm our identity.
// The publicKeyHint tells the server which public key to use to verify the signature.
var result = await CallPlayFabLoginWithHello(publicKeyHint, signedChallenge);
if (result == null) return;
// Report the result.
await ShowMessage("Signed in with Session Ticket " + result.Result.SessionTicket);
}
public async Task<string> GetPublicKeyBase64(KeyCredential userCredential)
{
IBuffer publicKey = userCredential.RetrievePublicKey();
if (publicKey == null)
{
await ShowMessage("Failed to get public key for credential");
return null;
}
return CryptographicBuffer.EncodeToBase64String(publicKey);
}
public string GetPublicKeyHintBase64(KeyCredential userCredential)
{
HashAlgorithmProvider hashProvider = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
var publicKey = userCredential.RetrievePublicKey();
IBuffer publicKeyHash = hashProvider.HashData(publicKey);
return CryptographicBuffer.EncodeToBase64String(publicKeyHash);
}
public async Task<KeyCredential> GetUserCredentials(string userId)
{
// Open credential based on our Username and make sure it is successful
KeyCredentialRetrievalResult retrieveResult = await KeyCredentialManager.OpenAsync(userId);
if (retrieveResult.Status != KeyCredentialStatus.Success)
{
await ShowMessage("Error: Unable to open credentials! " + retrieveResult.Status);
return null;
}
return retrieveResult.Credential;
}
public async Task<string> GetPlayFabHelloChallenge(string publicKeyHint)
{
// Request challenge from PlayFab and make sure response has no errors
var challengeResponse = await PlayFab.PlayFabClientAPI.GetWindowsHelloChallengeAsync(new GetWindowsHelloChallengeRequest
{
PublicKeyHint = publicKeyHint,
TitleId = PlayFab.PlayFabSettings.TitleId
});
if (challengeResponse.Error != null)
{
await ShowMessage($"Error during getting challenge: {challengeResponse.Error.Error}");
return null;
}
return challengeResponse.Result.Challenge;
}
public async Task<string> RequestUserSignChallenge(KeyCredential credentials, string challenge)
{
IBuffer challengeBuffer = CryptographicBuffer.DecodeFromBase64String(challenge);
KeyCredentialOperationResult opResult = await credentials.RequestSignAsync(challengeBuffer);
if (opResult.Status != KeyCredentialStatus.Success)
{
await ShowMessage("Failed sign the challenge string: " + opResult.Status);
return null;
}
return CryptographicBuffer.EncodeToBase64String(opResult.Result);
}
public async Task<PlayFabResult<LoginResult>> CallPlayFabLoginWithHello(string publicKeyHint, string signedChallenge)
{
var loginResponse = await PlayFab.PlayFabClientAPI.LoginWithWindowsHelloAsync(new LoginWithWindowsHelloRequest
{
ChallengeSignature = signedChallenge,
PublicKeyHint = publicKeyHint
});
if (loginResponse.Error != null)
{
await ShowMessage($"Failed to log in: {loginResponse.Error.Error}");
return null;
}
return loginResponse;
}
public IAsyncOperation<IUICommand> ShowMessage(string messageString)
{
MessageDialog message = new MessageDialog($"{messageString}");
return message.ShowAsync();
}
public async Task<PlayFabResult<LoginResult>> CallPlayFabRegisterWithHello(string publicKey, string username)
{
var hostNames = NetworkInformation.GetHostNames();
var localName = hostNames.FirstOrDefault(name => name.DisplayName.Contains(".local"));
string computerName = localName.DisplayName.Replace(".local", "");
var registerResult = await PlayFab.PlayFabClientAPI.RegisterWithWindowsHelloAsync(new RegisterWithWindowsHelloRequest
{
DeviceName = computerName,
PublicKey = publicKey,
UserName = username
});
if (registerResult.Error != null)
{
await ShowMessage(registerResult.Error.GenerateErrorReport());
return null;
}
return registerResult;
}
public async Task<KeyCredential> CreateKeyCredential(string username)
{
KeyCredentialRetrievalResult keyCreationResult = await KeyCredentialManager.RequestCreateAsync(username, KeyCredentialCreationOption.ReplaceExisting);
if (keyCreationResult.Status != KeyCredentialStatus.Success)
{
// User has authenticated with Windows Hello and the key credential is created.
await ShowMessage("Failed to create key credential: " + keyCreationResult.Status);
return null;
}
return keyCreationResult.Credential;
}
}
}
测试
运行应用程序:
- 输入您的用户名。
- 选择 Register With Hello 按钮。
按照 Windows 提供的说明进行身份验证。
- 确认身份后,将看到一条确认消息,说明此帐户注册并登录。
- 使用会话票证。
- 选择 Sign in With Hello 按钮。
按照 Windows 提供的说明进行身份验证。
- 确认身份后,将看到一条确认消息,说明此帐户已登录。
- 使用会话票证。
至此,您已成功将 PlayFab 集成到您的 UWP 应用程序中。