向 Xamarin.Forms 应用添加身份验证

在本教程中,将使用 Azure Active Directory 将 Microsoft 身份验证添加到应用。 在完成本教程之前,请确保已 创建项目并部署了后端

注意

由于 iOS 应用需要密钥链访问,你需要设置 iOS 预配配置文件。 如果使用模拟器) ,预配配置文件需要实际 iOS 设备或付费的 Apple 开发人员帐户 (。 如果由于此限制而无法使用身份验证,则可以跳过本教程,继续向 应用添加脱机访问

提示

尽管我们使用 Azure Active Directory 进行身份验证,但你可以将任何所需的身份验证库用于 Azure 移动应用。

向后端服务添加身份验证

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

若要为后端服务启用 Azure Active Directory 身份验证,需要:

  • 将应用程序注册到 Azure Active Directory。
  • 向 ASP.NET 6 后端项目添加身份验证检查。

注册应用程序

首先,在 Azure Active Directory 租户中注册 Web API,并按照以下步骤添加范围:

  1. 登录 Azure 门户

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

  3. 搜索并选择“Azure Active Directory” 。

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

    • 名称:输入应用程序的名称;例如 ,TodoApp 快速入门。 应用的用户将看到此名称。 稍后可对其进行更改。
    • 支持的帐户类型任何组织目录中 (任何 Azure AD 目录中的帐户 - 多租户) 和个人 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 中添加 MS 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 ,指示需要身份验证。

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

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

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

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

    在 Visual Studio 中添加 MS 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"
  },

完成后,它应如下所示:

{
  "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": "*"
}

<client-id> 替换为前面记录的应用程序 (客户端) ID。

完成后,可以再次发布服务:

  1. 右键单击 TodoAppService.NET6 项目,然后选择“ 发布到>Azure...”
  2. 选择后端服务,然后选择“ 发布 ”,将更新的站点发布到 Azure。

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

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

向应用程序添加身份验证

Microsoft Datasync Framework 内置了对在 HTTP 事务标头中使用 Json Web 令牌 (JWT) 的任何身份验证提供程序的支持。 此应用程序将使用 Microsoft 身份验证库 (MSAL) 请求此类令牌,并授权已登录的用户访问后端服务。

配置本机客户端应用程序

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

  1. Azure 门户中,选择“Active Directory”>“应用注册”>“新建注册”。

  2. “注册应用程序 ”页中:

    • 输入应用注册 的名称 。 可能需要使用 名称来区分此名称 native-quickstart 与后端服务使用的名称。
    • 选择任何组织目录中的帐户(任何 Azure AD 目录 - 多租户)和个人 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. 选中 (替换为{client-id}应用程序 ID) 旁边的msal{client-id}://auth框。

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

  11. 选择页面底部的“保存” 。

  12. 选择“概述”。 记下 应用程序 (客户端) ID ,因为稍后需要它。 这称为 本机客户端应用程序 ID

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

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

TodoApp.sln Visual Studio 中打开解决方案,并将 TodoApp.Forms 项目设置为启动项目。

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

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

  2. 选择“浏览”选项卡。

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

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

    在 Visual Studio 中选择 M S A L 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 Azure Active Directory
      /// </summary>
      public static string ApplicationId = "<client-id>";

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

<client-id>将 替换为在 Azure Active Directory 中注册客户端应用程序时收到的本机客户端应用程序 ID,将 <scope> 替换为在注册服务应用程序时使用公开 API 时复制的 Web API 范围

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

  2. 选择“浏览”选项卡。

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

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

    在 Visual Studio 中选择 M S A L 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 Azure Active Directory
      /// </summary>
      public static string ApplicationId = "<client-id>";

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

<client-id>将 替换为在 Azure Active Directory 中注册客户端应用程序时收到的值,并将 <scope> 替换为在注册服务应用程序时使用公开 API 时复制的范围。

打开 TodoApp.Forms 项目。 添加名为 IPlatform.cs 的新文件,其中包含以下内容:

using Microsoft.Identity.Client;

namespace TodoApp.Forms
{
    public interface IPlatform
    {
        IPublicClientApplication GetIdentityClient(string applicationId);
    }
}

此接口稍后将用于允许共享项目向平台项目请求适合平台的标识客户端。

打开 App.xaml.cs。 添加以下 using 语句:

using Microsoft.Datasync.Client;
using Microsoft.Identity.Client;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

在 类中 App ,添加两个新属性:

public IPublicClientApplication IdentityClient { get; set; }
public IPlatform PlatformService { get; }

调整构造函数以读取:

public App(IPlatform platformService)
{
    InitializeComponent();

    PlatformService = platformService;
    TodoService = new RemoteTodoService(GetAuthenticationToken);
    MainPage = new NavigationPage(new MainPage(this, TodoService));
}

GetAuthenticationToken 方法添加到类:

public async Task<AuthenticationToken> GetAuthenticationToken()
{
    if (IdentityClient == null)
    {
        IdentityClient = PlatformService.GetIdentityClient(Constants.ApplicationId);
    }

    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()
                .ConfigureAwait(false);
        }
        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 则会使用授权每个请求所需的数据生成 。 否则,将生成过期的不良令牌。

配置 Android 应用进行身份验证

打开 TodoApp.Forms.Android 项目。 使用以下代码创建新类 MsalActivity

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

namespace TodoApp.Forms.Droid
{
    [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} 与) 相同的 Constants.ApplicationId 本机客户端 (的应用程序 ID。

打开 MainActivity.cs。 将 添加到 IPlatform 类的定义:MainActivity

public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity, IPlatform

更改 LoadApplication() 方法中的 OnCreate() 调用:

protected override void OnCreate(Bundle savedInstanceState)
{
    base.OnCreate(savedInstanceState);

    Xamarin.Essentials.Platform.Init(this, savedInstanceState);
    global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
    LoadApplication(new App(this));
}

将以下代码添加到 类的底部:

protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{
    base.OnActivityResult(requestCode, resultCode, data);
    // Return control to MSAL
    AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(requestCode, resultCode, data);
}

public IPublicClientApplication GetIdentityClient(string applicationId)
{
    var identityClient = PublicClientApplicationBuilder.Create(applicationId)
        .WithAuthority(AzureCloudInstance.AzurePublic, "common")
        .WithRedirectUri($"msal{applicationId}://auth")
        .WithParentActivityOrWindow(() => this)
        .Build();
    return identityClient;
}

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

配置 iOS 应用进行身份验证

打开 AppDelegate.cs 项目中的 TodoApp.Forms.iOS 文件。 将 添加到 IPlatform 类的定义:AppDelegate

public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate, IPlatform

更改 FinishedLaunching() 方法以读取:

public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
    global::Xamarin.Forms.Forms.Init();
    LoadApplication(new App(this));
    return base.FinishedLaunching(app, options);
}

在此类的末尾添加以下代码:

public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
{
    bool result = AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(url);
    return result || base.OpenUrl(app, url, options);
}

public IPublicClientApplication GetIdentityClient(string applicationId)
{
    var identityClient = PublicClientApplicationBuilder.Create(applicationId)
        .WithIosKeychainSecurityGroup("com.microsoft.adalcache")
        .WithRedirectUri($"msal{applicationId}://auth")
        .Build();
    return identityClient;
}

添加对 的 Entitlements.plist密钥链访问权限:

  1. 打开 Entitlements.plist 文件。

  2. 选择“ 密钥链”。

  3. 在密钥链组中选择“ 新增 ”。

  4. 输入 com.microsoft.adalcache 作为值:

    显示 i O S 权利的屏幕截图。

将自定义权利添加到项目:

  1. 右键单击 TodoApp.Forms.iOS 项目,然后选择“ 属性”。

  2. 选择“ iOS 捆绑签名”。

  3. 选择“自定义权利”字段旁边的“...”按钮。

  4. 选择 Entitlements,然后选择 “打开”。

  5. Ctrl+S 保存项目。

    显示 i O S 捆绑签名属性的屏幕截图。

添加对 的 Entitlements.plist密钥链访问权限:

  1. 打开 Entitlements.plist 文件。

  2. 如有必要,请从 “源 ”视图切换到“ 权利” 视图。 选择器位于窗口的右上角。

  3. 向下滚动,直到找到 “密钥链 ”面板。

  4. 打开 密钥链 开关。

  5. 选择绿色 + 图标。

  6. 在提供的框中输入 com.microsoft.adalcache , (覆盖) 已有的任何内容,然后按 Enter。

    显示 macOS 上的 i O S 密钥链属性的屏幕截图。

将自定义权利添加到项目:

  1. 右键单击 TodoApp.Forms.iOS 项目,然后选择“ 选项”。

  2. 选择“ iOS 捆绑签名”。

  3. 选择“自定义权利”字段旁边的“...”按钮。

  4. 选择 TodoApp.Forms.iOS>Entitlements.plist,然后选择 “打开”。

  5. 选择“确定”。

    显示 i O S 捆绑包签名属性的屏幕截图。

测试 Android 应用

设置为 TodoApp.Forms.Android 启动项目,然后按 F5 生成并运行应用。 当应用启动时,系统会提示你登录到应用。 首次运行时,系统还会要求你同意该应用。 身份验证完成后,应用将正常运行。

测试 iOS 应用

注意

由于 iOS 应用需要密钥链访问,你需要设置预配配置文件。 如果使用模拟器) ,预配配置文件需要实际设备或付费的 Apple 开发人员帐户 (。 有关详细信息,请参阅 适用于 iOS 的设备预配

设置为 TodoApp.Forms.iOS 启动项目,然后按 F5 生成并运行应用。 当应用启动时,系统会提示你登录到应用。 首次运行时,系统还会要求你同意该应用。 身份验证完成后,应用将正常运行。

后续步骤

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

延伸阅读