使用通用 Windows 平台设置 PlayFab 身份验证

本教程指导您完成使用通用 Windows 平台 (UWP) 进行 PlayFab 身份验证的过程。

重要

此过程是对如何通过 Windows Hello 和 PlayFab 获取所有实体和提交身份验证的非常基本的介绍。 有关 Windows Hello 和 PlayFab 身份验证的更复杂示例,请考虑 [UWPExample project](https://github.com/PlayFab/UWPExample)

要求

注意

以下要求至关重要:使用 Windows 10 操作系统、使用经过验证的 Microsoft 帐户登录、配置好 PIN 等访问接口。 如果 满足上述要求,应用程序将失败,并且不提供任何有用的原因说明。

准备 Visual Studio 项目

启动 Visual Studio 并创建新项目。

  1. 模板下,选择 Windows 通用
  2. 然后选择空白应用(通用 Windows) 类型。
  3. 将其命名为 GettingStartedPlayfabUWP
  4. 选择确定按钮提交。

Visual Studio 新的空白 UWP 应用程序

  1. 选择与您的项目匹配的目标版本最低版本
  2. 选择“确定”按钮。

Visual Studio UWP SDK 版本

创建项目后,使用 NuGet 包管理器添加 PlayFab SDK

  1. 首先,选择工具选项卡。
  2. 在下拉菜单中,选择 NuGet 包管理器
  3. 然后选择管理解决方案的 NuGet 程序包

Visual Studio NuGet 包管理器

NuGet 管理器窗口中:

  1. 选择浏览并搜索 PlayFabAllSDK 包。
  2. 选择目标项目
  3. 然后选择安装按钮。

Visual Studio 安装 PlayFab SDK

完成后,您的基本项目设置即告完成。 在下一节中,将修改应在项目创建时自动生成的 2 个类:

  1. 应用
  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;
        }

    }



}

测试

运行应用程序:

  1. 输入您的用户名。
  2. 选择 Register With Hello 按钮。

UWP 示例 - 注册

按照 Windows 提供的说明进行身份验证。

  1. 确认身份后,将看到一条确认消息,说明此帐户注册并登录
  2. 使用会话票证

UWP 示例 - 注册确认

  1. 选择 Sign in With Hello 按钮。

UWP 示例 - 登录

按照 Windows 提供的说明进行身份验证。

  1. 确认身份后,将看到一条确认消息,说明此帐户已登录
  2. 使用会话票证

UWP 示例 - 登录确认

至此,您已成功将 PlayFab 集成到您的 UWP 应用程序中。