教程:使用外部租户在 .NET MAUI shell 应用中登录用户
本教程是系列教程的最后一部分,演示如何创建 .NET 多平台应用 UI (.NET MAUI) shell 应用并使用 Microsoft Entra 管理中心准备进行身份验证。 在本系列教程的第 2 部分中,你添加了自定义 Microsoft 身份验证库 (MSAL) 客户端帮助程序,以初始化 MSAL SDK、安装所需的库并包括图像资源。 此最后一步演示了如何在 .NET MAUI 中添加登录和注销代码,以及如何在 Android 平台上运行 shell 应用。
在本教程中,你将:
- 添加登录和注销代码。
- 修改应用 Shell。
- 添加特定于平台的代码。
- 添加应用设置。
- 运行并测试 .NET MAUI shell 应用。
先决条件
添加登录和注销代码
.NET MAUI 应用的用户界面 (UI) 由映射到每个目标平台的本机控件的对象构成。 用于创建 .NET MAUI 应用 UI 的主控件组包括页面、布局和视图。
添加主视图页
后续步骤将对代码进行组织,以便定义 main view
。
从不再需要 MainPage.xaml 和 MainPage.xaml.cs 的项目中删除这两者。 在“解决方案资源管理器”窗格中,找到 MainPage.xaml 的条目,右键单击它并选择“删除”。
右键单击“SignInMaui”项目,然后选择“新增”>“文件夹”。 将文件夹命名为 Views。
右键单击“视图”。
选择“添加”>“新项...”。
在模板列表中选择“.NET MAUI”。
选择“.NET MAUI ContentPage (XAML)”模板。 将文件命名为 MainView.xaml。
选择 添加 。
MainView.xaml 文件将在新的文档选项卡中打开,显示表示页面 UI 的所有 XAML 标记。 将 XAML 标记替换为以下标记:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="SignInMaui.Views.MainView" Title="Microsoft Entra External ID" > <Shell.BackButtonBehavior> <BackButtonBehavior IsVisible="False" IsEnabled="False" /> </Shell.BackButtonBehavior> <ScrollView> <VerticalStackLayout Spacing="25" Padding="30,0" VerticalOptions="Center"> <Image Source="external_id.png" SemanticProperties.Description="External ID" HeightRequest="200" HorizontalOptions="Center" /> <Label Text="CIAM" SemanticProperties.HeadingLevel="Level1" FontSize="26" HorizontalOptions="Center" /> <Label Text="MAUI sample" SemanticProperties.HeadingLevel="Level1" FontSize="26" HorizontalOptions="Center" /> <Button x:Name="SignInButton" Text="Sign In" SemanticProperties.Hint="Sign In" Clicked="OnSignInClicked" HorizontalOptions="Center" IsEnabled="False"/> </VerticalStackLayout> </ScrollView> </ContentPage>
保存文件。
让我们将页面上 XAML 控件的关键部分细分一下:
<ContentPage>
是 MainView 类的根对象。<VerticalStackLayout>
是 ContentPage 的子对象。 此布局控件将其子控件逐一垂直排列。<Image>
会显示一张图像,在本例中,它使用你之前下载的 azureactive_directory.png_。<Label>
控件显示文本。- 用户可按下
<Button>
来引发Clicked
事件。 可运行代码来响应Clicked
事件。 Clicked="OnSignInClicked"
:按钮的Clicked
事件分配给OnSignInClicked
事件处理程序,将在代码隐藏文件中定义该处理程序。 将在下一步骤中创建此代码。
处理 OnSignInClicked 事件
下一步是为按钮的 Clicked
事件添加代码。
在 Visual Studio 的“解决方案资源管理器”窗格中,展开 MainView.xaml 文件以显示其代码隐藏文件 MainView.xaml.cs。 打开 MainView.xaml.cs,并将文件的内容替换为以下代码:
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using SignInMaui.MSALClient; using Microsoft.Identity.Client; namespace SignInMaui.Views { public partial class MainView : ContentPage { public MainView() { InitializeComponent(); IAccount cachedUserAccount = PublicClientSingleton.Instance.MSALClientHelper.FetchSignedInUserFromCache().Result; _ = Dispatcher.DispatchAsync(async () => { if (cachedUserAccount == null) { SignInButton.IsEnabled = true; } else { await Shell.Current.GoToAsync("claimsview"); } }); } private async void OnSignInClicked(object sender, EventArgs e) { await PublicClientSingleton.Instance.AcquireTokenSilentAsync(); await Shell.Current.GoToAsync("claimsview"); } protected override bool OnBackButtonPressed() { return true; } } }
MainView
类是一个内容页面,负责显示应用的主视图。 在构造函数中,它使用PublicClientSingleton
实例中的MSALClientHelper
来检索缓存的用户帐户,如果未找到缓存的用户帐户,则启用“登录”按钮。单击“登录”按钮时,它会调用
AcquireTokenSilentAsync
方法来以无提示方式获取令牌,并使用Shell.Current.GoToAsync
方法导航到claimsview
页面。 此外,会重写OnBackButtonPressed
方法以返回 true,表示已为此视图禁用后退按钮。
添加声明视图页
后续步骤将对代码进行组织,以便定义 ClaimsView
页面。 该页面将显示在 ID 令牌中找到的用户声明。
在 Visual Studio 的“解决方案资源管理器”窗格中,右键单击“视图”。
选择“添加”>“新项...”。
在模板列表中选择“.NET MAUI”。
选择“.NET MAUI ContentPage (XAML)”模板。 将文件命名为 ClaimsView.xaml。
选择 添加 。
ClaimsView.xaml 文件将在新的文档选项卡中打开,显示表示页面 UI 的所有 XAML 标记。 将 XAML 标记替换为以下标记:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="SignInMaui.Views.ClaimsView" Title="ID Token View"> <Shell.BackButtonBehavior> <BackButtonBehavior IsVisible="False" IsEnabled="False" /> </Shell.BackButtonBehavior> <VerticalStackLayout> <Label Text="CIAM" FontSize="26" HorizontalOptions="Center" /> <Label Text="MAUI sample" FontSize="26" Padding="0,0,0,20" HorizontalOptions="Center" /> <Label Padding="0,20,0,0" VerticalOptions="Center" HorizontalOptions="Center" FontSize="18" Text="Claims found in ID token" /> <ListView ItemsSource="{Binding IdTokenClaims}" x:Name="Claims"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <Grid Padding="0, 0, 0, 0"> <Label Grid.Column="1" Text="{Binding}" HorizontalOptions="Center" /> </Grid> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> <Button x:Name="SignOutButton" Text="Sign Out" HorizontalOptions="Center" Clicked="SignOutButton_Clicked" /> </VerticalStackLayout> </ContentPage>
此 XAML 标记代码表示 .NET MAUI 应用中声明视图的 UI 布局。 它首先使用标题定义
ContentPage
并禁用后退按钮行为。在
VerticalStackLayout
中,有几个Label
元素显示静态文本,后跟一个名为Claims
的ListView
,它绑定到名为IdTokenClaims
的集合以显示在 ID 令牌中找到的声明。 每个声明都使用DataTemplate
在ViewCell
内呈现,并在网格中居中显示为Label
。最后,布局底部中间有一个
Sign Out
按钮,单击时会触发SignOutButton_Clicked
事件处理程序。
处理 ClaimsView 数据
下一步是添加代码来处理 ClaimsView
数据。
在 Visual Studio 的“解决方案资源管理器”窗格中,展开 ClaimsView.xaml 文件以显示其代码隐藏文件 ClaimsView.xaml.cs。 打开 ClaimsView.xaml.cs,并将文件的内容替换为以下代码:
using SignInMaui.MSALClient; using Microsoft.Identity.Client; namespace SignInMaui.Views; public partial class ClaimsView : ContentPage { public IEnumerable<string> IdTokenClaims { get; set; } = new string[] {"No claims found in ID token"}; public ClaimsView() { BindingContext = this; InitializeComponent(); _ = SetViewDataAsync(); } private async Task SetViewDataAsync() { try { _ = await PublicClientSingleton.Instance.AcquireTokenSilentAsync(); IdTokenClaims = PublicClientSingleton.Instance.MSALClientHelper.AuthResult.ClaimsPrincipal.Claims.Select(c => c.Value); Claims.ItemsSource = IdTokenClaims; } catch (MsalUiRequiredException) { await Shell.Current.GoToAsync("claimsview"); } } protected override bool OnBackButtonPressed() { return true; } private async void SignOutButton_Clicked(object sender, EventArgs e) { await PublicClientSingleton.Instance.SignOutAsync().ContinueWith((t) => { return Task.CompletedTask; }); await Shell.Current.GoToAsync("mainview"); } }
ClaimsView.xaml.cs 代码表示 .NET MAUI 应用中声明视图的代码隐藏。 它首先导入必要的命名空间,并定义用于扩展
ContentPage
的ClaimsView
类。IdTokenClaims
属性是可枚举的字符串,最初设置为单个字符串,指示找不到声明。ClaimsView
构造函数将绑定上下文设置为当前实例,初始化视图组件,并异步调用SetViewDataAsync
方法。SetViewDataAsync
方法尝试以无提示方式获取令牌,从身份验证结果中检索声明,并将IdTokenClaims
属性设置为在名为Claims
的ListView
中显示这些声明。 如果发生MsalUiRequiredException
,指示身份验证需要用户交互,则应用将导航到声明视图。OnBackButtonPressed
方法会重写后退按钮行为以始终返回 true,从而防止用户从此视图中返回。SignOutButton_Clicked
事件处理程序使用PublicClientSingleton
实例将用户注销,完成后导航到main view
。
修改应用 Shell
AppShell
类定义应用的视觉层次结构,即用于创建应用 UI 的 XAML 标记。 更新 AppShell
,使其了解 Views
。
在“解决方案资源管理器”窗格中双击
AppShell.xaml
文件来打开 XAML 编辑器。 将 XAML 标记替换为以下代码:<?xml version="1.0" encoding="UTF-8" ?> <Shell x:Class="SignInMaui.AppShell" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:SignInMaui.Views" Shell.FlyoutBehavior="Disabled"> <ShellContent Title="Home" ContentTemplate="{DataTemplate local:MainView}" Route="MainPage" /> </Shell>
XAML 代码定义一个
AppShell
类,该类禁用浮出控件行为,并将主内容设置为标题为Home
的ShellContent
元素和指向MainView
类的内容模板。在 Visual Studio 的“解决方案资源管理器”窗格中,展开 AppShell.xaml 文件以显示其代码隐藏文件 AppShell.xaml.cs。 打开 AppShell.xaml.cs,并将文件的内容替换为以下代码:
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using SignInMaui.Views; namespace SignInMaui; public partial class AppShell : Shell { public AppShell() { InitializeComponent(); Routing.RegisterRoute("mainview", typeof(MainView)); Routing.RegisterRoute("claimsview", typeof(ClaimsView)); } }
更新
AppShell.xaml.cs
文件,使其包含MainView
和ClaimsView
的必要路由注册。 通过调用InitializeComponent()
方法,可确保AppShell
类的初始化。RegisterRoute()
方法将mainview
和claimsview
路由与其各自的视图类型(MainView
和ClaimsView
)相关联。
添加特定于平台的代码
.NET MAUI 应用项目包含一个 Platforms 文件夹,其中每个子文件夹表示一个 .NET MAUI 可以面向的平台。 若要提供特定于 Android 应用程序的行为来对默认应用程序类进行补充,请执行以下步骤:
在“解决方案资源管理器”窗格中双击
Platforms/Android/AndroidManifest.xml
文件来打开 XML 编辑器。 更新以下属性:- 将应用程序名称设置为 MAUI CIAM。
- 将包名称设置为 SignInMaui.Droid。
- 将最低 Android 版本设置为“Android 5.0 (API 级别 21)”。
在“解决方案资源管理器”窗格中双击
Platforms/Android/MainActivity.cs
文件来打开 csharp 编辑器。 将文件内容替换为以下代码:// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using Android.App; using Android.Content; using Android.Content.PM; using Android.OS; using SignInMaui.MSALClient; using Microsoft.Identity.Client; namespace SignInMaui; [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] public class MainActivity : MauiAppCompatActivity { protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); // configure platform specific params PlatformConfig.Instance.RedirectUri = $"msal{PublicClientSingleton.Instance.MSALClientHelper.AzureAdConfig.ClientId}://auth"; PlatformConfig.Instance.ParentWindow = this; // Initialize MSAL and platformConfig is set _ = Task.Run(async () => await PublicClientSingleton.Instance.MSALClientHelper.InitializePublicClientAppAsync()).Result; } protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(requestCode, resultCode, data); } }
让我们将已添加的代码的关键部分细分一下:
- 必需的
using
语句包含在顶部。 MainActivity
类已定义,它继承自MauiAppCompatActivity
,而后者是 .NET MAUI 中 Android 平台的基类。- [Activity] 属性应用于
MainActivity
类,为 Android 活动指定各种设置。Theme = "@style/Maui.SplashTheme"
设置活动的初始主题。MainLauncher = true
将此活动指定为应用程序的主要入口点。ConfigurationChanges
指定活动可处理的配置更改,例如屏幕大小、方向、UI 模式、屏幕布局、最小屏幕大小和密度。
OnCreate
方法会被重写,以在创建活动时提供自定义逻辑。base.OnCreate(savedInstanceState)
调用方法的基实现。PlatformConfig.Instance.RedirectUri
设置为基于PublicClientSingleton.Instance.MSALClientHelper.AzureAdConfig.ClientId
动态生成的值。 它配置 MSAL 客户端的重定向 URI。PlatformConfig.Instance.ParentWindow
设置为当前活动实例,该实例指定身份验证相关操作的父窗口。PublicClientSingleton.Instance.MSALClientHelper.InitializePublicClientAppAsync()
使用名为MSALClientHelper
的单一实例中的帮助程序方法异步初始化 MSAL 客户端应用。Task.Run
用于在后台线程上执行初始化,.Result
用于同步等待任务完成。
OnActivityResult
方法会被重写,以处理当前活动启动的活动的结果。base.OnActivityResult(requestCode, resultCode, data)
调用方法的基实现。AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(requestCode, resultCode, data)
根据收到的请求代码、结果代码和意向数据设置身份验证延续事件参数。 这用于在外部活动返回结果后继续执行身份验证流。
- 必需的
在 Visual Studio 的“解决方案资源管理器”窗格中,选择“平台”。
右键单击 Android 文件夹 >“添加”>“新项...”。
选择“C# 项”>“类”。 命名文件
MsalActivity.cs
。将
MsalActivity.cs
文件的内容替换为以下代码:// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System; using System.Collections.Generic; using System.Linq; using System.Text; using Android.App; using Android.Content; using Android.OS; using Android.Runtime; using Android.Views; using Android.Widget; using Microsoft.Identity.Client; namespace MauiAppBasic.Platforms.Android.Resources { [Activity(Exported =true)] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataHost = "auth", DataScheme = "msalEnter_the_Application_Id_Here")] public class MsalActivity : BrowserTabActivity { } }
让我们将已添加的代码的关键部分细分一下:
MsalActivity
类是在MauiAppBasic.Platforms.Android.Resources
命名空间中声明的。 该类继承自BrowserTabActivity
类,表示它扩展了其功能。- 该类使用
[Activity(Exported = true)]
属性进行修饰,该属性表示活动已导出且可通过其他方法访问。 - 意向筛选器使用“[IntentFilter(...)]”属性进行指定。 它将活动配置为截获
ActionView
意向。 - 意向筛选器设置为使用指定的
DataScheme
(msalEnter_the_Application_Id_Here
) 和DataHost
(auth) 处理ActionView
意向。 此配置允许活动通过截获和处理ActionView
意向来处理身份验证过程。 将Enter_the_Application_Id_Here
替换为之前注册的应用的应用程序(客户端)ID。
添加应用设置
设置允许将配置应用行为的数据与代码分离,从而允许在不重新生成应用的情况下更改行为。 MauiAppBuilder
提供 ConfigurationManager
以在 .NET MAUI 应用中配置设置。 让我们将 appsettings.json
文件添加为 EmbeddedResource
。
若要创建 appsettings.json
,请执行以下步骤:
在 Visual Studio 的“解决方案资源管理器”窗格中,右键单击“SignInMaui”项目 >“添加”>“新项...”。
选择“Web”>“JavaScript JSON 配置文件”。 命名文件
appsettings.json
。选择 添加 。
选择 appsettings.json
在“属性”窗格中,将“生成操作”设置为“嵌入的资源”。
在“属性”窗格中,将“复制到输出目录”设置为“始终复制”。
将
appsettings.json
文件的内容替换为以下代码:{ "AzureAd": { "Authority": "https://Enter_the_Tenant_Subdomain_Here.ciamlogin.com/", "ClientId": "Enter_the_Application_Id_Here", "CacheFileName": "msal_cache.txt", "CacheDir": "C:/temp" }, "DownstreamApi": { "Scopes": "openid offline_access" } }
在
appsettings.json
中,找到占位符:- 查找
Enter_the_Tenant_Subdomain_Here
并将其替换为目录(租户)子域。 例如,如果租户主域为contoso.onmicrosoft.com
,请使用contoso
。 如果没有租户名称,请了解如何读取租户详细信息。 Enter_the_Application_Id_Here
,并将其替换为之前注册的应用的应用程序(客户端)ID。
- 查找
使用自定义 URL 域(可选)
使用自定义域可完全标记身份验证 URL。 从用户的角度来看,用户在身份验证过程中仍留在你的域中,而不是重定向到 ciamlogin.com 域名。
按照以下步骤使用自定义域:
使用为外部租户中的应用启用自定义 URL 域中的步骤为外部租户启用自定义 URL 域。
打开 appsettings.json 文件:
- 将
Authority
属性的值更新为 https://Enter_the_Custom_Domain_Here/Enter_the_Tenant_ID_Here。 请将Enter_the_Custom_Domain_Here
替换为你的自定义 URL 域,并将Enter_the_Tenant_ID_Here
替换为你的租户 ID。 如果没有租户 ID,请了解如何读取租户详细信息。 - 添加值为 [Enter_the_Custom_Domain_Here] 的
knownAuthorities
属性。
- 将
对 appsettings.json 文件进行更改后,如果自定义 URL 域为 login.contoso.com 且租户 ID 为 aaaabbbb-0000-cccc-1111-dddd2222eeee,则文件应类似于以下代码片段:
{
"AzureAd": {
"Authority": "https://login.contoso.com/aaaabbbb-0000-cccc-1111-dddd2222eeee",
"ClientId": "Enter_the_Application_Id_Here",
"CacheFileName": "msal_cache.txt",
"CacheDir": "C:/temp",
"KnownAuthorities": ["login.contoso.com"]
},
"DownstreamApi": {
"Scopes": "openid offline_access"
}
}
运行并测试 .NET MAUI 移动应用
根据设计,.NET MAUI 应用可在多个操作系统和设备上运行。 你需要选择要用于测试和调试应用的目标。
在 Visual Studio 工具栏中,将“调试目标”设置为要用于调试和测试的设备。 以下步骤演示如何将调试目标设置为 Android:
- 选择“调试目标”下拉列表。
- 选择“Android Emulator”。
- 选择仿真器设备。
按 F5 运行应用,或者选择 Visual Studio 顶部的播放按钮。
现在可以测试示例 .NET MAUI Android 应用。 运行应用后,Android 应用窗口将显示在模拟器中:
在显示的 Android 窗口中,选择“登录”按钮。 随即将打开一个浏览器窗口,提示你登录。
在登录过程中,系统会提示你授予各种权限(以允许应用程序访问数据)。 成功登录并同意后,应用程序屏幕会显示主页。