Web 帐户管理器Web Account Manager

本文介绍如何使用 AccountsSettingsPane 将通用 WINDOWS 平台 (UWP) 应用连接到外部标识提供者(如 Microsoft 或 Facebook),使用 Windows 10 Web 帐户管理器 api。This article describes how to use the AccountsSettingsPane to connect your Universal Windows Platform (UWP) app to external identity providers, like Microsoft or Facebook, using the Windows 10 Web Account Manager APIs. 你将了解如何请求用户的权限以使用其 Microsoft 帐户、获取访问令牌,并使用它来执行基本操作(如获取配置文件数据或将文件上传到他们的 OneDrive 帐户)。You'll learn how to request a user's permission to use their Microsoft account, obtain an access token, and use it to perform basic operations (like get profile data or upload files to their OneDrive account). 相关步骤类似于通过支持 Web 帐户管理器的任何标识提供者来获取用户权限和访问权限。The steps are similar for getting user permission and access with any identity provider that supports the Web Account Manager.

备注

有关完整代码示例,请参阅 GitHub 上的 WebAccountManagement 示例For a complete code sample, see the WebAccountManagement sample on GitHub.

准备工作Get set up

首先,在 Visual Studio 中创建一个新的空白应用。First, create a new, blank app in Visual Studio.

第二,你需要将你的应用与应用商店关联,以便连接到标识提供者。Second, in order to connect to identity providers, you'll need to associate your app with the Store. 为此,请右键单击项目 ,选择 " 将 > 应用与应用商店关联",然后按照向导的说明进行操作。To do this, right click your project, choose Store > Associate app with the store, and follow the wizard's instructions.

第三,创建一个非常基本的 UI,由一个简单的 XAML 按钮和两个文本框组成。Third, create a very basic UI consisting of a simple XAML button and two text boxes.

<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
    <Button x:Name="LoginButton" Content="Log in" Click="LoginButton_Click" />
    <TextBlock x:Name="UserIdTextBlock"/>
    <TextBlock x:Name="UserNameTextBlock"/>
</StackPanel>

并在代码隐藏中创建一个附加到按钮的事件处理程序:And an event handler attached to your button in the code-behind:

private void LoginButton_Click(object sender, RoutedEventArgs e)
{   
}

最后,添加以下命名空间,以便你以后无需担心任何引用问题:Lastly, add the following namespaces so you don't have to worry about any reference issues later:

using System;
using Windows.Security.Authentication.Web.Core;
using Windows.System;
using Windows.UI.ApplicationSettings;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.Data.Json;
using Windows.UI.Xaml.Navigation;
using Windows.Web.Http;

显示帐户设置窗格Show the accounts settings pane

系统提供了一个名为 AccountsSettingsPane 的内置用户界面,用于管理标识提供者和 Web 帐户。The system provides a built-in user interface for managing identity providers and web accounts called AccountsSettingsPane. 你可以按如下方式显示它:You can show it like this:

private void LoginButton_Click(object sender, RoutedEventArgs e)
{
    AccountsSettingsPane.Show(); 
}

如果你运行应用并单击“登录”按钮,应该会显示一个空窗口。If you run your app and click the "Log in" button, it should display an empty window.

未列出任何帐户的 "选择帐户" 窗口的屏幕截图。

窗格为空的原因是系统只提供了一个 UI shell,这取决于开发人员是否使用标识提供者以编程方式填充窗格。The pane is empty because the system only provides a UI shell - it's up to the developer to programatically populate the pane with the identity providers.

提示

或者,您可以使用 ShowAddAccountAsync 而不是 Show,这将返回 IAsyncAction 以查询操作的状态。Optionally, you can use ShowAddAccountAsync instead of Show, which will return an IAsyncAction, to query for the status of the operation.

注册 AccountCommandsRequestedRegister for AccountCommandsRequested

若要向窗格添加命令,请先注册 AccountCommandsRequested 事件处理程序。To add commands to the pane, we start by registering for the AccountCommandsRequested event handler. 这会告知系统在用户要求查看窗格时运行生成逻辑 (例如,单击 XAML 按钮) 。This tells the system to run our build logic when the user asks to see the pane (for example, clicks our XAML button).

在隐藏代码中,替代 OnNavigatedTo 和 OnNavigatedFrom 事件,并向它们添加以下代码:In your code behind, override the OnNavigatedTo and OnNavigatedFrom events and add the following code to them:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    AccountsSettingsPane.GetForCurrentView().AccountCommandsRequested += BuildPaneAsync; 
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    AccountsSettingsPane.GetForCurrentView().AccountCommandsRequested -= BuildPaneAsync; 
}

用户不会与帐户非常频繁地交互,因而以此种方式注册和取消注册事件处理程序有助于防止内存泄漏。Users don't interact with accounts very often, so registering and deregistering your event handler in this fashion helps prevent memory leaks. 这样,当用户需要自定义窗格的可能性比较大时(例如,当他们在“设置”或“登录”页面时),自定义窗格只会在内存中。This way, your customized pane is only in memory when there's a high chance a user is going to ask for it (because they're on a "settings" or "login" page, for example).

生成帐户设置窗格Build the account settings pane

只要显示 AccountsSettingsPane,就会调用 BuildPaneAsync 方法。The BuildPaneAsync method is called whenever the AccountsSettingsPane is shown. 这是我们放置代码来自定义窗格中显示的命令的位置。This is where we'll put the code to customize the commands shown in the pane.

首先获取延迟。Start by obtaining a deferral. 这将告知系统延迟显示 AccountsSettingsPane,直到我们完成它的生成为止。This tells the system to delay showing the AccountsSettingsPane until we're finished building it.

private async void BuildPaneAsync(AccountsSettingsPane s,
    AccountsSettingsPaneCommandsRequestedEventArgs e)
{
    var deferral = e.GetDeferral();
        
    deferral.Complete(); 
}

接下来,使用 WebAuthenticationCoreManager.FindAccountProviderAsync 方法获取提供程序。Next, get a provider using the WebAuthenticationCoreManager.FindAccountProviderAsync method. 提供程序的 URL 因提供程序而异,并且可以在提供程序的文档中找到。The URL for the provider varies based on the provider and can be found in the provider's documentation. 对于 Microsoft 帐户和 Azure Active Directory,它是 "https : //login.microsoft.com"。For Microsoft Accounts and Azure Active Directory, it's "https://login.microsoft.com".

private async void BuildPaneAsync(AccountsSettingsPane s,
    AccountsSettingsPaneCommandsRequestedEventArgs e)
{
    var deferral = e.GetDeferral();
        
    var msaProvider = await WebAuthenticationCoreManager.FindAccountProviderAsync(
        "https://login.microsoft.com", "consumers"); 
        
    deferral.Complete(); 
}

请注意,我们还向可选 authority 参数传递字符串“consumers”。Notice that we also pass the string "consumers" to the optional authority parameter. 这是因为 Microsoft 提供了两种不同类型的身份验证,即适用于“消费者”的 Microsoft 帐户 (MSA) 和适用于“组织”的 Azure Active Directory (AAD)。This is because Microsoft provides two different types of authentication - Microsoft Accounts (MSA) for "consumers", and Azure Active Directory (AAD) for "organizations". “consumers”颁发机构指示我们需要 MSA 选项。The "consumers" authority indicates we want the MSA option. 如果在开发一款企业应用,请使用“organizations”字符串。If you're developing an enterprise app, use the string "organizations" instead.

最后,通过创建新的**WebAccountProviderCommand** (如下所示)将提供程序添加到AccountsSettingsPaneFinally, add the provider to the AccountsSettingsPane by creating a new WebAccountProviderCommand like this:

private async void BuildPaneAsync(AccountsSettingsPane s,
    AccountsSettingsPaneCommandsRequestedEventArgs e)
{
    var deferral = e.GetDeferral();

    var msaProvider = await WebAuthenticationCoreManager.FindAccountProviderAsync(
        "https://login.microsoft.com", "consumers");

    var command = new WebAccountProviderCommand(msaProvider, GetMsaTokenAsync);  

    e.WebAccountProviderCommands.Add(command);

    deferral.Complete(); 
}

我们传递给新 WebAccountProviderCommand 的 GetMsaToken 方法尚不存在(我们将在下一步骤中生成该方法),因此当前可以将其作为空方法添加。The GetMsaToken method we passed to our new WebAccountProviderCommand doesn't exist yet (we'll build that in the next step), so feel free to add it as an empty method for now.

运行上述代码,你的窗格应当如下所示:Run the above code and your pane should look something like this:

"选择包含帐户的帐户" 窗口的屏幕截图。

请求令牌Request a token

“Microsoft 帐户”选项显示在 AccountsSettingsPane 中后,我们需要处理用户选择它时发生的情况。Once we have the Microsoft Account option displaying in the AccountsSettingsPane, we need to handle what happens when the user selects it. 我们注册了 GetMsaToken 方法,以便在用户选择使用其 Microsoft 帐户登录时触发,这样我们会在此时获得令牌。We registered our GetMsaToken method to fire when the user chooses to log in with their Microsoft Account, so we'll obtain the token there.

若要获取令牌,请使用 RequestTokenAsync 方法,如下所示:To obtain a token, use the RequestTokenAsync method like this:

private async void GetMsaTokenAsync(WebAccountProviderCommand command)
{
    WebTokenRequest request = new WebTokenRequest(command.WebAccountProvider, "wl.basic");
    WebTokenRequestResult result = await WebAuthenticationCoreManager.RequestTokenAsync(request);
}

在此示例中,我们将字符串 "wl. basic" 传递到 scope 参数。In this example, we pass the string "wl.basic" to the scope parameter. 作用域表示你正在从提供给特定用户的服务请求的信息类型。Scope represents the type of information you are requesting from the providing service on a specific user. 某些作用域仅提供对用户基本信息的访问权限,例如姓名和电子邮件地址,而其他作用域可能允许访问敏感信息,如用户的照片或电子邮件收件箱。Certain scopes provide access only to a user's basic information, like name and email address, while other scopes might grant access to sensitive information such as the user's photos or email inbox. 通常,应用应该至少使用实现其功能所必需的最小许可作用域。Generally, your app should use the least permissive scope necessary to achieve its function. 有关需要哪些作用域才能获得用以与它们的服务结合使用的令牌,服务提供商将提供相关文档。Service providers will provide documentation on which scopes are needed to get tokens for use with their services.

提示

(可选)如果你的应用使用登录提示 (用默认电子邮件地址填充 "user" 字段) 或与登录体验相关的其他特殊属性,请在 " AppProperties " 属性中列出。Optionally, if your app uses a login hint (to populate the user field with a default email address) or other special property related to the sign-in experience, list it in the WebTokenRequest.AppProperties property. 这将导致系统在缓存 web 帐户时忽略属性,这会阻止缓存中的帐户不匹配。This will cause the system to ignore the property when caching the web account, which prevents account mismatches in the cache.

如果在开发企业应用,可能需要连接到 Azure Active Directory (AAD) 实例,并使用 Microsoft Graph API,而非常规的 MSA 服务。If you're developing an enterprise app, you'll likely want to connect to an Azure Active Directory (AAD) instance and use the Microsoft Graph API instead of regular MSA services. 在此方案中,使用以下代码:In this scenario, use the following code instead:

private async void GetAadTokenAsync(WebAccountProviderCommand command)
{
    string clientId = "your_guid_here"; // Obtain your clientId from the Azure Portal
    WebTokenRequest request = new WebTokenRequest(provider, "User.Read", clientId);
    request.Properties.Add("resource", "https://graph.microsoft.com");
    WebTokenRequestResult result = await WebAuthenticationCoreManager.RequestTokenAsync(request);
}

本文的剩余部分将继续介绍 MSA 方案,但 AAD 代码非常相似。The rest of this article continues describing the MSA scenario, but the code for AAD is very similar. 有关 AAD/Graph 的详细信息,包括 GitHub 上的完整示例,请参阅 Microsoft Graph 文档For more info on AAD/Graph, including a full sample on GitHub, see the Microsoft Graph documentation.

使用令牌Use the token

RequestTokenAsync 方法会返回一个 WebTokenRequestResult 对象,它包含你请求的结果。The RequestTokenAsync method returns a WebTokenRequestResult object, which contains the results of your request. 如果请求成功,它将包含一个令牌。If your request was successful, it will contain a token.

private async void GetMsaTokenAsync(WebAccountProviderCommand command)
{
    WebTokenRequest request = new WebTokenRequest(command.WebAccountProvider, "wl.basic");
    WebTokenRequestResult result = await WebAuthenticationCoreManager.RequestTokenAsync(request);
    
    if (result.ResponseStatus == WebTokenRequestStatus.Success)
    {
        string token = result.ResponseData[0].Token; 
    }
}

备注

如果在请求令牌时收到错误,请确保已将应用与 Microsoft Store 关联,如步骤一中所述。If you receive an error when requesting a token, make sure you've associated your app with the Store as described in step one. 如果你跳过此步骤,你的应用将无法获取令牌。Your app won't be able to get a token if you skipped this step.

获得令牌后,可以使用它来调用提供商的 API。Once you have a token, you can use it to call your provider's API. 在以下代码中,我们将调用用户信息 Microsoft Live API,以获取有关用户的基本信息并将其显示在我们的 UI 中。In the code below, we'll call the user info Microsoft Live API to obtain basic information about the user and display it in our UI. 但请注意,在大多数情况下,建议获取令牌后立即存储,然后以单独的方法使用该令牌。Note however that in most cases it is recommended that you store the token once obtained and then use it in a separate method.

private async void GetMsaTokenAsync(WebAccountProviderCommand command)
{
    WebTokenRequest request = new WebTokenRequest(command.WebAccountProvider, "wl.basic");
    WebTokenRequestResult result = await WebAuthenticationCoreManager.RequestTokenAsync(request);
    
    if (result.ResponseStatus == WebTokenRequestStatus.Success)
    {
        string token = result.ResponseData[0].Token; 
        
        var restApi = new Uri(@"https://apis.live.net/v5.0/me?access_token=" + token);

        using (var client = new HttpClient())
        {
            var infoResult = await client.GetAsync(restApi);
            string content = await infoResult.Content.ReadAsStringAsync();

            var jsonObject = JsonObject.Parse(content);
            string id = jsonObject["id"].GetString();
            string name = jsonObject["name"].GetString();

            UserIdTextBlock.Text = "Id: " + id; 
            UserNameTextBlock.Text = "Name: " + name;
        }
    }
}

在提供程序之间,调用不同 REST API 的方式均不相同;有关如何使用令牌的信息,请参阅提供程序的 API 文档。How you call various REST APIs varies between providers; see the provider's API documentation for information on how to use your token.

存储帐户以供将来使用Store the account for future use

令牌可用于立即获取有关用户的信息,但它们通常具有不同的生命期,例如,MSA 令牌的有效期只有几个小时。Tokens are useful for immediately obtaining information about a user, but they usually have varying lifespans - MSA tokens, for instance, are only valid for a few hours. 幸运的是,每当令牌过期时,都不需要重新显示 AccountsSettingsPaneFortunately, you don't need to re-show the AccountsSettingsPane each time a token expires. 在用户对你的应用进行一次授权后,你可以存储用户的帐户信息,以供将来使用。Once a user has authorized your app once, you can store the user's account information for future use.

若要执行此操作,请使用 WebAccount 类。To do this, use the WebAccount class. 将以用于请求令牌的相同方法返回 WebAccountA WebAccount is returned by the same method you used to request the token:

private async void GetMsaTokenAsync(WebAccountProviderCommand command)
{
    WebTokenRequest request = new WebTokenRequest(command.WebAccountProvider, "wl.basic");
    WebTokenRequestResult result = await WebAuthenticationCoreManager.RequestTokenAsync(request);
    
    if (result.ResponseStatus == WebTokenRequestStatus.Success)
    {
        WebAccount account = result.ResponseData[0].WebAccount; 
    }
}

获得 WebAccount 实例后,即可轻松存储它。Once you have a WebAccount instance, you can easily store it. 在下面的示例中,我们使用 LocalSettings。In the following example, we use LocalSettings. 有关使用 LocalSettings 和其他方法来存储用户数据的详细信息,请参阅存储和检索应用设置和数据For more information on using LocalSettings and other methods to store user data, see Store and retrieve app settings and data.

private async void StoreWebAccount(WebAccount account)
{
    ApplicationData.Current.LocalSettings.Values["CurrentUserProviderId"] = account.WebAccountProvider.Id;
    ApplicationData.Current.LocalSettings.Values["CurrentUserId"] = account.Id; 
}

然后,我们可以使用如下所示的异步方法,通过存储的 WebAccount 尝试在后台获取令牌。Then, we can use an asynchronous method like the following to attempt to obtain a token in the background with the stored WebAccount.

private async Task<string> GetTokenSilentlyAsync()
{
    string providerId = ApplicationData.Current.LocalSettings.Values["CurrentUserProviderId"]?.ToString();
    string accountId = ApplicationData.Current.LocalSettings.Values["CurrentUserId"]?.ToString();

    if (null == providerId || null == accountId)
    {
        return null; 
    }

    WebAccountProvider provider = await WebAuthenticationCoreManager.FindAccountProviderAsync(providerId);
    WebAccount account = await WebAuthenticationCoreManager.FindAccountAsync(provider, accountId);

    WebTokenRequest request = new WebTokenRequest(provider, "wl.basic");

    WebTokenRequestResult result = await WebAuthenticationCoreManager.GetTokenSilentlyAsync(request, account);
    if (result.ResponseStatus == WebTokenRequestStatus.UserInteractionRequired)
    {
        // Unable to get a token silently - you'll need to show the UI
        return null; 
    }
    else if (result.ResponseStatus == WebTokenRequestStatus.Success)
    {
        // Success
        return result.ResponseData[0].Token;
    }
    else
    {
        // Other error 
        return null; 
    }
}

将上述方法置于构建 AccountsSettingsPane 的代码之前。Place the above method just before the code that builds the AccountsSettingsPane. 如果已在后台获取令牌,则不需要显示该窗格。If the token is obtained in the background, there is no need to show the pane.

private void LoginButton_Click(object sender, RoutedEventArgs e)
{
    string silentToken = await GetMsaTokenSilentlyAsync();

    if (silentToken != null)
    {
        // the token was obtained. store a reference to it or do something with it here.
    }
    else
    {
        // the token could not be obtained silently. Show the AccountsSettingsPane
        AccountsSettingsPane.Show();
    }
}

由于采用静默方式获取令牌非常简单,所以你应该使用此过程来刷新会话之间的令牌,而不是缓存现有的令牌(因为令牌可能会随时过期)。Because obtaining a token silently is very simple, you should use this process to refresh your token between sessions rather than caching an existing token (since that token might expire at any time).

备注

上述示例中仅介绍了基本的成功和失败情况。The example above only covers basic success and fail cases. 你的应用还应当考虑特殊情况(例如,用户吊销你的应用的权限或者从 Windows 中删除他们的帐户),并进行合理处理。Your app should also account for unusual scenarios (like a user revoking your app's permission or removing their account from Windows, for example) and handle them gracefully.

删除已存储帐户Remove a stored account

如果保留 web 帐户,你可能希望用户能够解除他们的帐户与你的应用之间的关联。If you persist a web account, you may want to give your users the ability to disassociate their account with your app. 这样,他们就可以有效地 "注销" 应用程序:启动时,将不再自动加载其帐户信息。This way, they can effectively "log out" of the app: their account information will no longer be loaded automatically upon launch. 若要执行此操作,首先从存储中删除任何保存的帐户和提供商信息。To do this, first remove any saved account and provider information from storage. 然后,调用 SignOutAsync 清除缓存,并使应用可能拥有的任何现有令牌失效。Then call SignOutAsync to clear the cache and invalidate any existing tokens your app may have.

private async Task SignOutAccountAsync(WebAccount account)
{
    ApplicationData.Current.LocalSettings.Values.Remove("CurrentUserProviderId");
    ApplicationData.Current.LocalSettings.Values.Remove("CurrentUserId"); 
    account.SignOutAsync(); 
}

添加不支持 WebAccountManager 的提供程序Add providers that don't support WebAccountManager

例如,如果你想要将身份验证从服务集成到你的应用,但该服务不支持 WebAccountManager-Google + 或 Twitter,则你仍可以将该提供程序手动添加到 AccountsSettingsPane中。If you want to integrate authentication from a service into your app but that service doesn't support WebAccountManager - Google+ or Twitter, for example - you can still manually add that provider to the AccountsSettingsPane. 若要执行此操作,请新建 WebAccountProvider 对象,并提供自己的名称和 .png 图标,然后将其添加到 WebAccountProviderCommands 列表。To do so, create a new WebAccountProvider object and provide your own name and .png icon, then and add it to the WebAccountProviderCommands list. 下面是一些存根代码:Here's some stub code:

private async void BuildPaneAsync(AccountsSettingsPane s, AccountsSettingsPaneCommandsRequestedEventArgs e)
{
   // other code here 

   var twitterProvider = new WebAccountProvider("twitter", "Twitter", new Uri(@"ms-appx:///Assets/twitter-auth-icon.png")); 
   var twitterCmd = new WebAccountProviderCommand(twitterProvider, GetTwitterTokenAsync);
   e.WebAccountProviderCommands.Add(twitterCmd);   
   
   // other code here
}

private async void GetTwitterTokenAsync(WebAccountProviderCommand command)
{
   // Manually handle Twitter login here
}

备注

这只会将图标添加到 AccountsSettingsPane 并在单击该图标时运行指定的方法(在本例中为 GetTwitterTokenAsync)。This only adds an icon to the AccountsSettingsPane and runs the method you specify when the icon is clicked (GetTwitterTokenAsync, in this case). 必须提供用于处理实际身份验证的代码。You must provide the code that handles the actual authentication. 有关详细信息,请参阅 Web 身份验证代理,它提供使用 REST 服务进行身份验证的帮助器方法。For more information, see Web authentication broker, which provides helper methods for authenticating using REST services.

添加自定义标题Add a custom header

你可以使用 HeaderText 属性自定义帐户设置窗格,如下所示:You can customize the account settings pane using the HeaderText property, like this:

private async void BuildPaneAsync(AccountsSettingsPane s, AccountsSettingsPaneCommandsRequestedEventArgs e)
{
    // other code here 
    
    args.HeaderText = "MyAwesomeApp works best if you're signed in.";   
    
    // other code here
}

如果你登录,请选择 "选择没有帐户的帐户" 窗口的屏幕截图,并显示一条消息,其中显示了 "我的卓越应用程序"。

标题文本不要过于冗长;保持简洁明了。Don't go overboard with header text; keep it short and sweet. 如果你的登录过程很复杂并且需要显示详细信息,请使用自定义链接将用户链接到单独的页面。If your login process is complicated and you need to display more information, link the user to a separate page using a custom link.

你可以向 AccountsSettingsPane 添加自定义命令,它们会在受支持的 WebAccountProviders 下方显示为链接。You can add custom commands to the AccountsSettingsPane, which appear as links below your supported WebAccountProviders. 自定义命令非常适合与用户帐户相关的简单任务,如显示隐私策略或为遇到问题的用户启动支持页面。Custom commands are great for simple tasks related to user accounts, like displaying a privacy policy or launching a support page for users having trouble.

下面是一个示例:Here's an example:

private async void BuildPaneAsync(AccountsSettingsPane s, AccountsSettingsPaneCommandsRequestedEventArgs e)
{
    // other code here 
    
    var settingsCmd = new SettingsCommand(
        "settings_privacy", 
        "Privacy policy", 
        async (x) => await Launcher.LaunchUriAsync(new Uri(@"https://privacy.microsoft.com/en-US/"))); 

    e.Commands.Add(settingsCmd); 
    
    // other code here
}

未列出帐户的 "选择帐户" 窗口的屏幕截图和指向隐私策略的链接。

理论上,你可以对任何内容使用设置命令。Theoretically, you can use settings commands for anything. 但是,我们建议将它们的使用范围限制在直观的与帐户相关的情况,如上文所述。However, we suggest limiting their use to intuitive, account-related scenarios like those described above.

请参阅See also

Windows.Security.Authentication.Web.Core 命名空间Windows.Security.Authentication.Web.Core namespace

Windows.Security.Credentials 命名空间Windows.Security.Credentials namespace

AccountsSettingsPane 类AccountsSettingsPane class

Web 身份验证代理Web authentication broker

Web 帐户管理示例Web account management sample

Lunch Scheduler 应用Lunch Scheduler app