使用 Azure Active Directory B2C 对用户进行身份验证

Azure Active Directory B2C 为面向使用者的 Web 和移动应用提供云标识管理。 本文介绍了如何配合使用 Microsoft 身份验证库和 Azure Active Directory B2C 将标识管理集成到移动应用程序中。

概述

Azure Active Directory B2C (ADB2C) 是面向使用者的应用程序的标识管理服务。 它允许用户使用其现有的社交帐户或自定义凭据(例如电子邮件或用户名和密码)登录到应用程序。 自定义凭据帐户称为本地帐户

将 Azure Active Directory B2C 标识管理服务集成到移动应用程序的过程如下:

  1. 创建 Azure Active Directory B2C 租户。
  2. 向 Azure Active Directory B2C 租户注册移动应用程序。
  3. 创建注册和登录策略,以及忘记密码用户流。
  4. 使用 Microsoft 身份验证库 (MSAL) 通过 Azure Active Directory B2C 租户启动身份验证工作流。

注意

如果还没有 Azure 订阅,可以在开始前创建一个免费帐户

Azure Active Directory B2C 支持多个标识提供者,包括 Microsoft、GitHub、Facebook、Twitter 等。 有关 Azure Active Directory B2C 功能的详细信息,请参阅 Azure Active Directory B2C 文档

Microsoft 身份验证库支持多个应用程序体系结构和平台。 有关 MSAL 功能的信息,请参阅 GitHub 上的 Microsoft 身份验证库

配置 Azure Active Directory B2C 租户

若要运行示例项目,必须创建 Azure Active Directory B2C 租户。 有关详细信息,请参阅在 Azure 门户中创建 Azure Active Directory B2C 租户

创建租户后,需要使用“租户名称”和“租户 ID”来配置移动应用程序。 租户 ID 和名称由创建租户 URL 时生成的域定义。 如果生成的租户 URL 为 https://contoso20190410tenant.onmicrosoft.com/,则“租户 ID”为 contoso20190410tenant.onmicrosoft.com,“租户名称”为 contoso20190410tenant。 单击顶部菜单中的“目录和订阅筛选器”,在 Azure 门户中查找租户域。 以下屏幕截图显示了 Azure 的“目录和订阅筛选器”按钮和租户域:

Azure 目录和订阅筛选器视图中的租户名称

在示例项目中,编辑 Constants.cs 文件以设置 tenantNametenantId 字段。 以下代码显示了租户域为 https://contoso20190410tenant.onmicrosoft.com/ 时应如何设置这些值,请将这些值替换为门户中的值:

public static class Constants
{
    static readonly string tenantName = "contoso20190410tenant";
    static readonly string tenantId = "contoso20190410tenant.onmicrosoft.com";
    ...
}

向 Azure Active Directory B2C 注册移动应用程序

必须先向租户注册移动应用程序,然后才能连接该应用程序并对用户进行身份验证。 注册过程向应用程序分配唯一的“应用程序 ID”,并分配一个“重定向 URL”以在身份验证后将响应定向回应用程序。 有关详细信息,请参阅 Azure Active Directory B2C:注册应用程序。 你需要知道分配给应用程序的“应用程序 ID”,其在属性视图中的应用程序名称之后列出。 以下屏幕截图显示了在何处可以找到“应用程序 ID”:

Azure 应用程序属性视图中的应用程序 ID

Microsoft 身份验证库要求应用程序的“重定向 URL”是“应用程序 ID”加上前缀文本“msal”,后跟名为“auth”的终结点。 如果“应用程序 ID”为“1234abcd”,则完整 URL 应为 msal1234abcd://auth。 确保应用程序已启用“本机客户端”设置,并使用“应用程序 ID”创建“自定义重定向 URI”,如以下屏幕截图中所示

Azure 应用程序属性视图中的自定义重定向 URI

稍后在 Android ApplicationManifest.xml 和 iOS Info.plist 中将会用到该 URL

在示例项目中,编辑 Constants.cs 文件,将 clientId 字段设置为“应用程序 ID”。 以下代码演示了在“应用程序 ID”为 1234abcd 的情况下应如何设置此值:

public static class Constants
{
    static readonly string tenantName = "contoso20190410tenant";
    static readonly string tenantId = "contoso20190410tenant.onmicrosoft.com";
    static readonly string clientId = "1234abcd";
    ...
}

创建注册和登录策略,以及忘记密码策略

策略是用户在完成创建帐户或重置密码等任务期间所经历的体验。 策略还指定当用户从体验中返回时应用程序收到的令牌的内容。 必须设置帐户注册和登录策略,并重置密码。 Azure 具有内置策略,可简化常见策略的创建。 有关详细信息,请参阅 Azure Active Directory B2C:内置策略

完成策略设置后,Azure 门户的“用户流(策略)”视图中应当有两个策略。 以下屏幕截图演示了 Azure 门户中的两个已配置的策略:

Azure 用户流(策略)视图中的两个已配置策略

在示例项目中,编辑 Constants.cs 文件,设置 policySigninpolicyPassword 字段以反映策略设置期间选择的名称:

public static class Constants
{
    static readonly string tenantName = "contoso20190410tenant";
    static readonly string tenantId = "contoso20190410tenant.onmicrosoft.com";
    static readonly string clientId = "1234abcd";
    static readonly string policySignin = "B2C_1_signupsignin1";
    static readonly string policyPassword = "B2C_1_passwordreset";
    ...
}

使用 Microsoft 身份验证库 (MSAL) 进行身份验证

必须将 Microsoft 身份验证库 (MSAL) NuGet 包添加到共享的 .NET Standard 项目以及 Xamarin.Forms 解决方案中的平台项目。 MSAL 包含 PublicClientApplicationBuilder 类,该类用于构造粘附到 IPublicClientApplication 接口的对象。 MSAL 利用 With 子句向构造函数和身份验证方法提供其他参数。

在示例项目中,代码隐藏文件 App.xaml 定义名为 AuthenticationClientUIParent 的静态属性,并实例化构造函数中的 AuthenticationClient 对象WithIosKeychainSecurityGroup 子句为 iOS 应用程序提供安全组名称。 WithB2CAuthority 子句提供将用于对用户进行身份验证的默认“授权”或策略WithRedirectUri 子句告知 Azure 通知中心实例在指定多个 URI 时应使用哪个“重定向 URI”。 以下示例演示了如何实例化 PublicClientApplication

public partial class App : Application
{
    public static IPublicClientApplication AuthenticationClient { get; private set; }

    public static object UIParent { get; set; } = null;

    public App()
    {
        InitializeComponent();

        AuthenticationClient = PublicClientApplicationBuilder.Create(Constants.ClientId)
            .WithIosKeychainSecurityGroup(Constants.IosKeychainSecurityGroups)
            .WithB2CAuthority(Constants.AuthoritySignin)
            .WithRedirectUri($"msal{Constants.ClientId}://auth")
            .Build();

        MainPage = new NavigationPage(new LoginPage());
    }

    ...

注意

如果 Azure 通知中心实例仅定义了一个“重定向 URI”,则 AuthenticationClient 实例可以正常工作,而无需使用 WithRedirectUri 子句指定“重定向 URI”。 但是,如果 Azure 配置扩展以支持其他客户端或身份验证方法,应始终指定此值。

代码隐藏文件 LoginPage.xaml.cs 中的 OnAppearing 事件处理程序会调用 AcquireTokenSilentAsync,以刷新之前登录的用户的身份验证令牌。 如果成功,身份验证过程会重定向到 LogoutPage,若失败则不执行任何操作。 以下示例演示了 OnAppearing 中的无提示重新身份验证过程:

public partial class LoginPage : ContentPage
{
    ...

    protected override async void OnAppearing()
    {
        try
        {
            // Look for existing account
            IEnumerable<IAccount> accounts = await App.AuthenticationClient.GetAccountsAsync();

            AuthenticationResult result = await App.AuthenticationClient
                .AcquireTokenSilent(Constants.Scopes, accounts.FirstOrDefault())
                .ExecuteAsync();

            await Navigation.PushAsync(new LogoutPage(result));
        }
        catch
        {
            // Do nothing - the user isn't logged in
        }
        base.OnAppearing();
    }

    ...
}

OnLoginButtonClicked 事件处理程序(单击“登录”按钮时触发)会调用 AcquireTokenAsync。 MSAL 库会自动打开移动设备浏览器并导航到登录页。 登录 URL 称为“授权”,是 Constants.cs 文件中定义的租户名称和策略的组合。 如果用户选择忘记密码选项,它们则会返回给应用并出现一个异常,从而启动忘记密码体验。 以下示例演示了身份验证过程:

public partial class LoginPage : ContentPage
{
    ...

    async void OnLoginButtonClicked(object sender, EventArgs e)
    {
        AuthenticationResult result;
        try
        {
            result = await App.AuthenticationClient
                .AcquireTokenInteractive(Constants.Scopes)
                .WithPrompt(Prompt.SelectAccount)
                .WithParentActivityOrWindow(App.UIParent)
                .ExecuteAsync();

            await Navigation.PushAsync(new LogoutPage(result));
        }
        catch (MsalException ex)
        {
            if (ex.Message != null && ex.Message.Contains("AADB2C90118"))
            {
                result = await OnForgotPassword();
                await Navigation.PushAsync(new LogoutPage(result));
            }
            else if (ex.ErrorCode != "authentication_canceled")
            {
                await DisplayAlert("An error has occurred", "Exception message: " + ex.Message, "Dismiss");
            }
        }
    }

    ...
}

OnForgotPassword 方法类似于登录过程,但会实现自定义策略。 OnForgotPassword 使用不同的 AcquireTokenAsync 重载,这允许你提供特定的“授权”。 以下示例演示了如何在获取令牌时提供自定义“授权”

public partial class LoginPage : ContentPage
{
    ...
    async Task<AuthenticationResult> OnForgotPassword()
    {
        try
        {
            return await App.AuthenticationClient
                .AcquireTokenInteractive(Constants.Scopes)
                .WithPrompt(Prompt.SelectAccount)
                .WithParentActivityOrWindow(App.UIParent)
                .WithB2CAuthority(Constants.AuthorityPasswordReset)
                .ExecuteAsync();
        }
        catch (MsalException)
        {
            // Do nothing - ErrorCode will be displayed in OnLoginButtonClicked
            return null;
        }
    }
}

最后一部分身份验证是退出登录过程。 OnLogoutButtonClicked 方法在用户按下“退出登录”按钮时调用。 该方法遍历所有帐户,并确保其令牌已失效。 下面的示例演示了退出登录实现:

public partial class LogoutPage : ContentPage
{
    ...
    async void OnLogoutButtonClicked(object sender, EventArgs e)
    {
        IEnumerable<IAccount> accounts = await App.AuthenticationClient.GetAccountsAsync();

        while (accounts.Any())
        {
            await App.AuthenticationClient.RemoveAsync(accounts.First());
            accounts = await App.AuthenticationClient.GetAccountsAsync();
        }

        await Navigation.PopAsync();
    }
}

iOS

在 iOS 上,向 Azure Active Directory B2C 注册的自定义 URL 方案必须在 Info.plist 中进行注册。 MSAL 要求 URL 方案遵循之前在向 Azure Active Directory B2C 注册移动应用程序中描述的特定模式。 以下屏幕截图显示了 Info.plist 中的自定义 URL 方案

“在 iOS 上注册自定义 URL 方案”

在 iOS 上,MSAL 还需要密钥链权利(在 Entitilements.plist 中注册),如以下屏幕截图中所示

“在 iOS 上设置应用程序权利”

当 Azure Active Directory B2C 完成授权请求时,它会重定向到已注册的重定向 URL。 自定义 URL 方案会使 iOS 启动移动应用程序,并将 URL 作为启动参数传入,该参数由应用程序 AppDelegate 类的 OpenUrl 替代进行处理,并将体验的控制权交还给 MSAL。 下面的代码示例演示了 OpenUrl 实现:

using Microsoft.Identity.Client;

namespace TodoAzure.iOS
{
    [Register("AppDelegate")]
    public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
    {
        ...
        public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
        {
            AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(url);
            return base.OpenUrl(app, url, options);
        }
    }
}

Android

在 Android 上,向 Azure Active Directory B2C 注册的自定义 URL 方案必须在 AndroidManifest.xml 中进行注册。 MSAL 要求 URL 方案遵循之前在向 Azure Active Directory B2C 注册移动应用程序中描述的特定模式。 以下示例演示了 AndroidManifest.xml 中的自定义 URL 方案

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.xamarin.adb2cauthorization">
  <uses-sdk android:minSdkVersion="15" />
  <application android:label="ADB2CAuthorization">
    <activity android:name="microsoft.identity.client.BrowserTabActivity">
      <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <!-- example -->
        <!-- <data android:scheme="msalaaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" android:host="auth" /> -->
        <data android:scheme="INSERT_URI_SCHEME_HERE" android:host="auth" />
      </intent-filter>
    </activity>"
  </application>
</manifest>

必须修改 MainActivity 类才能在 OnCreate 调用期间向应用程序提供 UIParent 对象。 当 Azure Active Directory B2C 完成授权请求时,它会重定向到 AndroidManifest.xml 中注册的 URL 方案。 注册 URL 方案会使 Android 调用 OnActivityResult 方法,并将 URL 用作启动参数,该参数由 SetAuthenticationContinuationEventArgs 方法处理。

public class MainActivity : FormsAppCompatActivity
{
    protected override void OnCreate(Bundle bundle)
    {
        TabLayoutResource = Resource.Layout.Tabbar;
        ToolbarResource = Resource.Layout.Toolbar;

        base.OnCreate(bundle);

        Forms.Init(this, bundle);
        LoadApplication(new App());
        App.UIParent = this;
    }

    protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
    {
        base.OnActivityResult(requestCode, resultCode, data);
        AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(requestCode, resultCode, data);
    }
}

通用 Windows 平台

在通用 Windows 平台上使用 MSAL 无需额外的设置

运行项目

在虚拟或物理设备上运行应用程序。 点击“登录”按钮应打开浏览器,并导航到可以登录或创建帐户的页面。 完成登录过程后,应返回到应用程序的注销页。 以下屏幕截图显示了在 Android 和 iOS 上运行的用户登录屏幕:

“Android 和 iOS 上的 Azure ADB2C 登录屏幕”