适用于: 员工租户
外部租户(了解详细信息)
本教程是一系列教程的最后一部分,演示如何创建 .NET 多平台应用 UI (.NET MAUI) shell 应用,并准备使用 Microsoft Entra 管理中心进行身份验证。 在本系列的第 2 部分中,添加了自定义Microsoft身份验证库(MSAL)客户端帮助程序来初始化 MSAL SDK、安装所需的库并包括映像资源。 最后一步演示如何在 .NET MAUI 中添加登录和注销代码并在 Android 平台上运行 shell 应用。
在本教程中,你将:
- 添加登录和注销代码。
- 修改应用程序的界面。
- 添加特定于平台的代码。
- 添加应用设置。
- 运行并测试 .NET MAUI shell 应用。
先决条件
添加登录和注销代码
.NET MAUI 应用的用户界面(UI)由映射到每个目标平台的本机控件的对象构造。 用于创建 .NET MAUI 应用的 UI 的主要控件组是页面、布局和视图。
添加主视图页
后续步骤将组织我们的代码,以定义 main view
。
请从项目中删除 MainPage.xaml 和 MainPage.xaml.cs,它们已不再需要。 在“解决方案资源管理器”窗格中,找到 MainPage.xaml 的条目,右键单击它并选择“删除”。
右键单击 SignInMaui 项目,然后选择“ 添加新>文件夹”。 将文件夹 命名为“视图”。
右键单击 视图。
选择“ 添加新>项...”。
在模板列表中选择 .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
类是一个内容页面,负责显示应用的主视图。 在构造函数中,它使用MSALClientHelper
PublicClientSingleton
实例检索缓存的用户帐户,并启用登录按钮(如果未找到缓存的用户帐户)。单击登录按钮时,它会调用
AcquireTokenSilentAsync
该方法以无提示方式获取令牌,并使用claimsview
该方法导航到Shell.Current.GoToAsync
页面。 此外,会重写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
元素显示静态文本,后跟一个名为ListView
的Claims
,它绑定到名为IdTokenClaims
的集合以显示在 ID 令牌中找到的声明。 每个声明都使用ViewCell
在DataTemplate
内呈现,并在网格中居中显示为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 应用中声明视图的后置代码。 它首先导入必要的命名空间并定义
ClaimsView
扩展的ContentPage
类。IdTokenClaims
属性是可枚举的字符串,最初设置为单个字符串,指示找不到声明。构造
ClaimsView
函数将绑定上下文设置为当前实例,初始化视图组件,并异步调用SetViewDataAsync
该方法。 该方法SetViewDataAsync
尝试静默获取令牌,从身份验证结果中获取声明,并设置IdTokenClaims
属性以在命名为ListView
的Claims
中显示这些声明。 如果发生MsalUiRequiredException
,指示身份验证需要用户交互,则应用将导航到声明视图。OnBackButtonPressed
方法会重写后退按钮行为以始终返回 true,从而防止用户从此视图中返回。SignOutButton_Clicked
事件处理程序使用PublicClientSingleton
实例将用户注销,完成后,导航到main view
该实例。
修改应用程序界面
该 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
类,该类禁用浮出控件行为,并将主内容设置为一个ShellContent
元素,具有标题Home
,并通过内容模板指向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 应用项目包含平台文件夹,每个子文件夹表示 .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
意向。 - 意向筛选器设置为使用指定的
ActionView
(DataScheme
) 和msalEnter_the_Application_Id_Here
("auth") 处理DataHost
意向。 此配置允许活动通过截获和处理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
并将其替换为 Directory (tenant) 子域。 例如,如果租户主域名是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,请了解如何读取租户详细信息。 - 添加
knownAuthorities
属性,并使用值 [Enter_the_Custom_Domain_Here]。
- 将属性的值
对 appsettings.json 文件进行更改后,如果自定义 URL 域 login.contoso.com,并且租户 ID 为 aaaabb-0000-cccc-1111-dd222eeee,则文件应类似于以下代码片段:
{
"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 窗口中,选择“登录”按钮。 随即将打开一个浏览器窗口,提示你登录。
在登录过程中,系统会提示你授予各种权限(以允许应用程序访问数据)。 成功登录并同意后,应用程序屏幕会显示主页。