将身份验证添加到 .NET MAUI 应用

注意

此产品已停用。 有关使用 .NET 8 或更高版本的项目的替换,请参阅 Community Toolkit Datasync 库

在本教程中,将使用 Microsoft Entra ID 向 TodoApp 项目添加Microsoft身份验证。 在完成本教程之前,请确保已 创建项目并部署后端

提示

尽管我们使用 Microsoft Entra ID 进行身份验证,但可以使用 Azure 移动应用所需的任何身份验证库。

将身份验证添加到后端服务

后端服务是标准 ASP.NET 6 服务。 演示如何为 ASP.NET 6 服务启用身份验证的任何教程都适用于 Azure 移动应用。

若要为后端服务启用Microsoft Entra 身份验证,需要:

  • 使用 Microsoft Entra ID 注册应用程序。
  • 向 ASP.NET 6 个后端项目添加身份验证检查。

注册应用程序

首先,在 Microsoft Entra 租户中注册 Web API,然后按照以下步骤添加范围:

  1. 登录到 Azure 门户

  2. 如果有权访问多个租户,请使用顶部菜单中的 目录 + 订阅 筛选器切换到要在其中注册应用程序的租户。

  3. 搜索并选择 Microsoft Entra ID

  4. 在“管理”下,选择 应用注册>新注册

    • 名称:输入应用程序的名称;例如,TodoApp 快速入门。 应用的用户将看到此名称。 稍后可以对其进行更改。
    • 支持的帐户类型任何组织目录中的帐户(任何Microsoft Entra 目录 - 多租户)和个人Microsoft帐户(例如Skype、Xbox)
  5. 选择 注册

  6. 在“管理”下,选择“公开 API>添加范围

  7. 对于 应用程序 ID URI,请选择 保存并继续,以接受默认值。

  8. 输入以下详细信息:

    • 范围名称access_as_user
    • 谁可以同意?管理员和用户
    • 管理员同意显示名称Access TodoApp
    • 管理员同意说明Allows the app to access TodoApp as the signed-in user.
    • 用户同意显示名称Access TodoApp
    • 用户同意说明Allow the app to access TodoApp on your behalf.
    • 状态:启用
  9. 选择“添加范围 以完成范围添加。

  10. 请注意范围的值,类似于 api://<client-id>/access_as_user(称为 Web API 范围)。 配置客户端时需要作用域。

  11. 选择 概述

  12. 请注意 Essentials 部分中 应用程序(客户端)ID(称为 Web API 应用程序 ID)。 需要此值才能配置后端服务。

打开 Visual Studio 并选择 TodoAppService.NET6 项目。

  1. 右键单击 TodoAppService.NET6 项目,然后选择 管理 NuGet 包...

  2. 在新选项卡中,选择“浏览”,然后在搜索框中输入 Microsoft.Identity.Web

    在 Visual Studio 中添加 M S A L NuGet 的屏幕截图。

  3. 选择 Microsoft.Identity.Web 包,然后按 安装

  4. 按照提示完成包的安装。

  5. 打开 Program.cs。 将以下内容添加到 using 语句列表:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;
  1. 直接在调用 builder.Services.AddDbContext()上方添加以下代码:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
  .AddMicrosoftIdentityWebApi(builder.Configuration);
builder.Services.AddAuthorization();
  1. 直接在调用 app.MapControllers()上方添加以下代码:
app.UseAuthentication();
app.UseAuthorization();

现在,Program.cs 应如下所示:

using Microsoft.AspNetCore.Datasync;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;
using TodoAppService.NET6.Db;
  
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
  
if (connectionString == null)
{
  throw new ApplicationException("DefaultConnection is not set");
}
  
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
  .AddMicrosoftIdentityWebApi(builder.Configuration);
builder.Services.AddAuthorization();
builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(connectionString));
builder.Services.AddDatasyncControllers();
  
var app = builder.Build();
  
// Initialize the database
using (var scope = app.Services.CreateScope())
{
  var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
  await context.InitializeDatabaseAsync().ConfigureAwait(false);
}
  
// Configure and run the web service.
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
  1. 编辑 Controllers\TodoItemController.cs。 向类添加 [Authorize] 属性。 你的类应如下所示:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Datasync;
using Microsoft.AspNetCore.Datasync.EFCore;
using Microsoft.AspNetCore.Mvc;
using TodoAppService.NET6.Db;

namespace TodoAppService.NET6.Controllers
{
  [Authorize]
  [Route("tables/todoitem")]
  public class TodoItemController : TableController<TodoItem>
  {
    public TodoItemController(AppDbContext context)
      : base(new EntityTableRepository<TodoItem>(context))
    {
    }
  }
}
  1. 编辑 appsettings.json。 添加以下块:
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com",
    "ClientId": "<client-id>",
    "TenantId": "common"
  },

<client-id> 替换为前面记录的 Web API 应用程序 ID。 完成后,它应如下所示:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com",
    "ClientId": "<client-id>",
    "TenantId": "common"
  },
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=TodoApp;Trusted_Connection=True"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

再次将服务发布到 Azure:

  1. 右键单击 TodoAppService.NET6 项目,然后选择 发布...
  2. 选择选项卡右上角的 “发布”按钮。

打开浏览器以 https://yoursite.azurewebsites.net/tables/todoitem?ZUMO-API-VERSION=3.0.0。 请注意,该服务现在返回一个 401 响应,该响应指示需要身份验证。

显示错误的浏览器的屏幕截图。

将应用注册到标识服务

Microsoft数据同步框架内置支持使用 HTTP 事务标头中的 Json Web 令牌(JWT)的任何身份验证提供程序。 此应用程序使用 Microsoft 身份验证库(MSAL) 请求此类令牌并授权已登录的用户访问后端服务。

配置本机客户端应用程序

可以使用客户端库(如Microsoft标识库(MSAL)注册本机客户端,以允许对应用中托管的 Web API 进行身份验证。

  1. Azure 门户中,选择“Microsoft 条目 ID>应用注册>新注册

  2. 注册应用程序 页:

    • 输入应用注册 名称。 可能需要使用名称 native-quickstart 将此名称与后端服务使用的名称区分开来。
    • 选择任何组织目录中的 帐户(任何Microsoft Entra 目录 - 多租户)和个人Microsoft帐户(例如Skype、Xbox)
    • 重定向 URI
      • 选择 公共客户端(移动 & 桌面)
      • 输入 URL quickstart://auth
  3. 选择 注册

  4. 选择 API 权限,>>“我的 API”添加权限。

  5. 选择之前为后端服务创建的应用注册。 如果未看到应用注册,请确保已添加 access_as_user 范围。

    Azure 门户中范围注册的屏幕截图。

  6. 选择权限下,选择 access_as_user,然后选择 添加权限

  7. 选择 身份验证移动和桌面应用程序。

  8. 选中 https://login.microsoftonline.com/common/oauth2/nativeclient旁边的框。

  9. 选中 msal{client-id}://auth 旁边的框(将 {client-id} 替换为应用程序 ID)。

  10. 选择“添加 URI,然后在字段中为额外的 URI 添加 http://localhost

  11. 选择页面底部 保存

  12. 选择 概述。 记下 应用程序(客户端)ID(称为 本机客户端应用程序 ID),因为需要它来配置移动应用。

我们定义了三个重定向 URL:

  • WPF 应用程序使用 http://localhost
  • UWP 应用程序使用 https://login.microsoftonline.com/common/oauth2/nativeclient
  • 移动(Android 和 iOS)应用程序使用 msal{client-id}://auth

将Microsoft标识客户端添加到应用

在 Visual Studio 中打开 TodoApp.sln 解决方案,并将 TodoApp.MAUI 项目设置为启动项目。 将 Microsoft 标识库(MSAL) 添加到 TodoApp.MAUI 项目:

Microsoft 标识库(MSAL) 添加到平台项目:

  1. 右键单击项目,然后选择 管理 NuGet 包...

  2. 选择“浏览”选项卡

  3. 在搜索框中输入 Microsoft.Identity.Client,然后按 Enter。

  4. 选择 Microsoft.Identity.Client 结果,然后单击 安装

    在 Visual Studio 中选择 MSAL NuGet 的屏幕截图。

  5. 接受许可协议以继续安装。

将本机客户端 ID 和后端范围添加到配置。

打开 TodoApp.Data 项目并编辑 Constants.cs 文件。 为 ApplicationIdScopes添加常量:

  public static class Constants
  {
      /// <summary>
      /// The base URI for the Datasync service.
      /// </summary>
      public static string ServiceUri = "https://demo-datasync-quickstart.azurewebsites.net";

      /// <summary>
      /// The application (client) ID for the native app within Microsoft Entra ID
      /// </summary>
      public static string ApplicationId = "<client-id>";

      /// <summary>
      /// The list of scopes to request
      /// </summary>
      public static string[] Scopes = new[]
      {
          "<scope>"
      };
  }

替换为在Microsoft Entra ID 中注册客户端应用程序时收到的 本机客户端应用程序 ID,并将 替换为在注册服务应用程序时使用 公开 API 时复制的 Web API 范围

TodoApp.MAUI 项目中打开 MainPage.xaml.cs 类。 添加以下 using 语句:

using Microsoft.Datasync.Client;
using Microsoft.Identity.Client;
using System.Diagnostics;

MainPage 类中,添加新属性:

public IPublicClientApplication IdentityClient { get; set; }

调整构造函数以读取:

public MainPage()
{
    InitializeComponent();
    TodoService = new RemoteTodoService(GetAuthenticationToken);
    viewModel = new MainViewModel(this, TodoService);
    BindingContext = viewModel;
}

GetAuthenticationToken 方法添加到类:

public async Task<AuthenticationToken> GetAuthenticationToken()
{
    if (IdentityClient == null)
    {
#if ANDROID
        IdentityClient = PublicClientApplicationBuilder
            .Create(Constants.ApplicationId)
            .WithAuthority(AzureCloudInstance.AzurePublic, "common")
            .WithRedirectUri($"msal{Constants.ApplicationId}://auth")
            .WithParentActivityOrWindow(() => Platform.CurrentActivity)
            .Build();
#elif IOS
        IdentityClient = PublicClientApplicationBuilder
            .Create(Constants.ApplicationId)
            .WithAuthority(AzureCloudInstance.AzurePublic, "common")
            .WithIosKeychainSecurityGroup("com.microsoft.adalcache")
            .WithRedirectUri($"msal{Constants.ApplicationId}://auth")
            .Build();
#else
        IdentityClient = PublicClientApplicationBuilder
            .Create(Constants.ApplicationId)
            .WithAuthority(AzureCloudInstance.AzurePublic, "common")
            .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient")
            .Build();
#endif
    }

    var accounts = await IdentityClient.GetAccountsAsync();
    AuthenticationResult result = null;
    bool tryInteractiveLogin = false;

    try
    {
        result = await IdentityClient
            .AcquireTokenSilent(Constants.Scopes, accounts.FirstOrDefault())
            .ExecuteAsync();
    }
    catch (MsalUiRequiredException)
    {
        tryInteractiveLogin = true;
    }
    catch (Exception ex)
    {
        Debug.WriteLine($"MSAL Silent Error: {ex.Message}");
    }

    if (tryInteractiveLogin)
    {
        try
        {
            result = await IdentityClient
                .AcquireTokenInteractive(Constants.Scopes)
                .ExecuteAsync();
        }
        catch (Exception ex)
        {
            Debug.WriteLine($"MSAL Interactive Error: {ex.Message}");
        }
    }

    return new AuthenticationToken
    {
        DisplayName = result?.Account?.Username ?? "",
        ExpiresOn = result?.ExpiresOn ?? DateTimeOffset.MinValue,
        Token = result?.AccessToken ?? "",
        UserId = result?.Account?.Username ?? ""
    };
}

GetAuthenticationToken() 方法适用于Microsoft标识库(MSAL),以获取适合将登录用户授权到后端服务的访问令牌。 然后,此函数将传递给用于创建客户端的 RemoteTodoService。 如果身份验证成功,则会生成 AuthenticationToken,其中包含授权每个请求所需的数据。 否则,将生成过期的错误的令牌。

可以使用具有平台说明符的 #if 区域添加任何特定于平台的选项。 例如,Android 要求我们指定从调用页传入的父活动。

配置用于身份验证的 Android 应用

使用以下代码创建新类 Platforms\Android\MsalActivity.cs

using Android.App;
using Android.Content;
using Microsoft.Identity.Client;

namespace TodoApp.MAUI
{
    [Activity(Exported = true)]
    [IntentFilter(new[] { Intent.ActionView },
        Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault },
        DataHost = "auth",
        DataScheme = "msal{client-id}")]
    public class MsalActivity : BrowserTabActivity
    {
    }
}

{client-id} 替换为本机客户端的应用程序 ID(与 Constants.ApplicationId相同)。

如果项目面向 Android 版本 11(API 版本 30)或更高版本,则必须更新 以满足Android 包可见性要求。 打开 Platforms/Android/AndroidManifest.xml,将以下 queries/intent 节点添加到 manifest 节点:

<manifest>
  ...
  <queries>
    <intent>
      <action android:name="android.support.customtabs.action.CustomTabsService" />
    </intent>
  </queries>
</manifest>

打开 MauiProgram.cs。 在文件顶部包含以下 using 语句:

using Microsoft.Identity.Client;

将生成器更新为以下代码:

    builder
        .UseMauiApp<App>()
        .ConfigureLifecycleEvents(events =>
        {
#if ANDROID
            events.AddAndroid(platform =>
            {
                platform.OnActivityResult((activity, rc, result, data) =>
                {
                    AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(rc, result, data);
                });
            });
#endif
        })
        .ConfigureFonts(fonts =>
        {
            fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
            fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
        });

如果在更新适用于 iOS 的应用程序后执行此步骤,请添加 #if ANDROID 指定的代码(包括 #if#endif)。 编译器根据正在编译的平台选取正确的代码片段。 可以在 iOS 的现有块之前或之后放置此代码。

当 Android 需要身份验证时,它会获取标识客户端,然后切换到打开系统浏览器的内部活动。 身份验证完成后,系统浏览器将重定向到定义的重定向 URL(msal{client-id}://auth)。 MsalActivity 捕获重定向 URL,然后通过调用 OnActivityResult()切换回主活动。 OnActivityResult() 方法调用 MSAL 身份验证帮助程序来完成事务。

测试 Android 应用

TodoApp.MAUI 设置为启动项目,选择 android 模拟器作为目标,然后按 F5 生成并运行应用。 应用启动时,系统会提示你登录到应用。 首次运行时,系统会要求你同意该应用。 身份验证完成后,应用将按正常方式运行。

测试 Windows 应用

TodoApp.MAUI 设置为启动项目,选择 Windows 计算机 作为目标,然后按 F5 生成并运行应用。 应用启动时,系统会提示你登录到应用。 首次运行时,系统会要求你同意该应用。 身份验证完成后,应用将按正常方式运行。

后续步骤

接下来,实现脱机存储,将应用程序配置为脱机运行。

进一步阅读