适用于 Android 的 ADAL 到 MSAL 迁移指南

本文重点介绍了将使用 Azure Active Directory 身份验证库(ADAL)的应用迁移为使用 Microsoft 身份验证库(MSAL)时需要进行的更改。

差异亮点

ADAL 与 Azure AD v1.0 终结点配合使用。 Microsoft 身份验证库(MSAL)可与 Microsoft 标识平台配合使用,该平台以前称为 Azure AD v2.0 终结点。 Microsoft 标识平台不同于 Azure AD v1.0,因为它:

支持:

  • 组织标识(Microsoft Entra ID)

  • 非组织标识,例如 Outlook.com、Xbox Live 等

  • (仅限 Azure AD B2C)与 Google、Facebook、X 和 Amazon 的联合登录

  • 是否兼容相关标准:

    • OAuth v2.0
    • OpenID Connect (OIDC)

MSAL 公共 API 引入了重要更改,包括:

  • 用于访问令牌的新模型:
    • ADAL 通过 AuthenticationContext 访问令牌,其中 AuthenticationContext 表示服务器。 MSAL 通过代表客户端的 PublicClientApplication 提供对令牌的访问。 客户端开发人员无需为每个需要与之交互的授权方创建新的 PublicClientApplication 实例。 只需要一个 PublicClientApplication 配置。
    • 除了资源标识符之外,还支持使用范围请求访问令牌。
    • 支持增量同意。 开发人员可以在用户访问应用中越来越多的功能时请求范围,包括应用注册期间不包括的功能。
    • 授权机构不再在运行时进行验证。 相反,开发人员在开发过程中声明了“已知当局”的列表。
  • 令牌 API 变更:
    • 在 ADAL 中, AcquireToken() 首先发出无提示请求。 如果失败,它会发出交互式请求。 这种行为导致某些开发人员仅依赖 AcquireToken,从而使系统有时会意外提示用户输入凭据。 MSAL 要求开发人员有意了解用户何时收到 UI 提示。
      • AcquireTokenSilent 始终会导致无提示请求成功或失败。
      • AcquireToken 始终会产生一个通过用户界面提示用户的请求。
  • MSAL 支持从默认浏览器或嵌入式 Web 视图登录:
    • 默认情况下,使用设备上的默认浏览器。 这样,MSAL 可以使用一个或多个已登录帐户可能已有的身份验证状态(Cookie)。 如果不存在身份验证状态,则在授权过程中通过 MSAL 进行身份验证会创建身份验证状态(Cookie),供将在同一浏览器中使用的其他 Web 应用使用。
  • 新的异常模型:
    • 异常更清楚地定义发生的错误类型以及开发人员需要执行哪些操作来解决它。
  • MSAL 支持在 AcquireTokenAcquireTokenSilent 调用中使用参数对象。
  • MSAL 支持对以下内容进行声明性配置:
    • 客户端 ID、重定向 URI。
    • 嵌入式浏览器与默认浏览器
    • Authorities
    • HTTP 设置,例如读取和连接超时

你的应用注册和迁移到 MSAL

无需更改现有应用注册以使用 MSAL。 如果想要利用增量/渐进式同意,可能需要查看注册,以确定想要以增量方式请求的特定范围。 有关范围和增量同意的详细信息,请参阅以下内容。

在门户中的应用注册中,你将看到 “API 权限 ”选项卡。这提供了应用当前配置为请求访问权限的 API 和权限(范围)列表。 它还显示与每个 API 权限关联的范围名称的列表。

使用 ADAL 和 Azure AD v1.0 终结点时,用户同意首次使用他们拥有的资源。 使用 MSAL 和Microsoft 标识平台,可以增量请求同意。 对于用户可能认为权限级别较高,或者在未清楚说明为何需要该权限时可能会产生疑问的权限,增量授权非常有用。 在 ADAL 中,这些权限可能会导致用户放弃登录到应用。

Tip

使用增量许可向用户提供有关应用为何需要权限的其他上下文。

组织管理员可以代表应用程序的所有成员同意应用程序所需的权限。 某些组织仅允许管理员同意应用程序。 管理员同意要求在应用注册中包含应用程序使用的所有 API 权限和范围。

Tip

即使你可以使用 MSAL 请求针对应用注册中未包含的资源的范围,我们仍建议你更新应用注册,以包含用户将来可能授予权限的所有资源和范围。

从资源 ID 迁移到作用域

对首次使用的所有权限进行身份验证和请求授权

如果当前使用的是 ADAL,并且不需要使用增量许可,则开始使用 MSAL 的最简单方法是使用新acquireToken对象发出AcquireTokenParameter请求并设置资源 ID 值。

Caution

无法同时设置范围和资源 ID。尝试设置这两者都将导致一个 IllegalArgumentException

这将产生与你所习惯的 v1 版本相同的行为。 应用注册中请求的所有权限都是在用户首次交互期间请求的。

仅根据需要进行身份验证和请求权限

若要利用增量同意,请列出你的应用在应用注册中使用的权限(范围),并根据以下条件将其整理为两个列表:

  • 在用户首次登录并与您的应用交互时,您希望请求哪些作用域。
  • 与应用重要功能关联的权限,你还需要向用户解释这些权限。

整理好各项权限范围后,按照你要为其请求令牌的资源(API)整理每个列表。 以及任何其他你希望用户同时一并授权的作用域。

用于向 MSAL 发出请求的参数对象支持:

  • Scope:要请求授权并获取访问令牌的作用域列表。
  • ExtraScopesToConsent:请求另一个资源访问令牌时要请求授权的范围的其他列表。 此范围列表允许最大程度地减少请求用户授权所需的次数。 这意味着用户授权或同意提示更少。

从 AuthenticationContext 迁移到 PublicClientApplications

构建 PublicClientApplication

使用 MSAL 时,需要实例化一个 PublicClientApplication。 此对象表示应用的标识信息,并用于向一个或多个授权机构发起请求。 使用此对象,你将配置客户端标识、重定向 URI、默认颁发机构、是否使用设备浏览器与嵌入式 Web 视图、日志级别等。

可以使用 JSON 以声明方式配置此对象,该 JSON 可以将其作为文件提供,也可以作为资源存储在 APK 中。

尽管此对象不是单例,但它在内部对交互式请求和静默请求都使用共享的 Executors

企业对企业

在 ADAL 中,你向其请求访问令牌的每个组织都需要一个单独的 AuthenticationContext 实例。 在 MSAL 中,这不再是一项要求。 在静默或交互式请求中,可以指定要从中请求令牌的授权机构。

从颁发机构验证迁移到已知颁发机构

MSAL 没有可用于启用或禁用颁发机构验证的标志。 颁发机构验证是 ADAL 和 MSAL 早期版本中的一项功能,可阻止代码从潜在的恶意颁发机构请求令牌。 MSAL 现在会获取 Microsoft 已知的颁发机构列表,并将该列表与你在配置中指定的颁发机构列表合并。

Tip

如果你是 Azure Business to Consumer (B2C) 用户,这意味着你不再需要禁用颁发机构验证。 相反,请在 MSAL 配置中将您所支持的每个 Azure AD B2C 策略都作为授权机构包含进去。 请注意,自 2025 年 5 月 1 日起,Azure AD B2C 将不再可供新客户购买。 若要了解详细信息,请参阅常见问题解答中的 Azure AD B2C 是否仍可供购买

如果你尝试使用某个 Microsoft 不知道且未包含在你的配置中的颁发机构,你会收到一个 UnknownAuthorityException

Logging

现在可以以声明方式将日志记录配置为配置的一部分,如下所示:

"logging": {
  "pii_enabled": false,
  "log_level": "WARNING",
  "logcat_enabled": true
}

从 UserInfo 迁移到帐户

在 ADAL 中,AuthenticationResult 提供一个 UserInfo 对象,用于检索有关已通过身份验证的帐户的信息。 “user”一词指人类用户或软件代理,但对该术语的使用方式使人难以清楚表达:某些应用支持单个用户(无论是人类用户还是软件代理)拥有多个账户。

请考虑使用银行帐户。 你可能在多个金融机构拥有多个帐户。 打开帐户时,系统会为每个帐户颁发凭据,例如 ATM 卡和 PIN,用于访问余额、帐单付款等。 这些凭据只能在颁发凭据的金融机构中使用。

与金融机构的帐户一样,Microsoft 标识平台中的帐户是使用凭据访问的。 这些凭据要么注册到Microsoft,要么由Microsoft颁发。 或者由 Microsoft 代表某个组织。

Microsoft 标识平台与金融机构不同的地方,在此类比中,Microsoft 标识平台提供了一个框架,允许用户使用一个帐户及其关联的凭据来访问属于多个个人和组织的资源。 这就像能够在另一家金融机构使用一家银行发行的卡。 这样做是因为所有有问题的组织都在使用Microsoft 标识平台,这允许跨多个组织使用一个帐户。 下面是一个示例:

Sam 在 Contoso.com 工作,但负责管理属于 Fabrikam.com 的 Azure 虚拟机。 若要让 Sam 管理 Fabrikam 的虚拟机,他需要获得访问权限才能访问它们。 可以通过将 Sam 的帐户添加到 Fabrikam.com,并授予其帐户一个允许他使用虚拟机的角色来授予此访问权限。 这可以通过Azure门户完成。

将 Sam 的 Contoso.com 帐户添加为 Fabrikam.com 的成员,将导致在 Fabrikam.com 的 Microsoft Entra ID 中为 Sam 创建一条新记录。 Sam 在 Microsoft Entra ID 中的记录称为用户对象。 在这种情况下,该用户对象将指回 Contoso.com 中 Sam 的用户对象。 Sam 的 Fabrikam 用户对象是 Sam 的本地表示形式,用于在 Fabrikam.com 上下文中存储与 Sam 关联的帐户的相关信息。 在 Contoso.com,Sam 的头衔是高级 DevOps 顾问。 在 Fabrikam,Sam 的职位是合同工 - 虚拟机。 在 Contoso.com 中,Sam 不负责或授权管理虚拟机。 在 Fabrikam.com,这是他唯一的工作职能。 然而,Sam 仍然只需要记住一组凭据,即由 Contoso.com 颁发的那组凭据。

成功进行acquireToken调用后,您将看到一个对IAccount对象的引用,可在后续的acquireTokenSilent请求中使用。

IMultiTenantAccount

如果你有一个应用,可以从该帐户所在的每个租户中访问关于该帐户的声明,则可以将 IAccount 对象强制转换为 IMultiTenantAccount。 此接口提供一个以租户 ID 为键、值为 ITenantProfiles 的映射,使你能够访问相对于当前帐户、在你已从中请求令牌的各个租户中属于该帐户的声明。

位于 IAccountIMultiTenantAccount 根部的声明始终包含来自主租户的声明。 如果您尚未在主租户中请求过令牌,则此集合为空。

其他更改

使用新版 AuthenticationCallback

// Existing ADAL Interface
public interface AuthenticationCallback<T> {

    /**
     * This will have the token info.
     *
     * @param result returns <T>
     */
    void onSuccess(T result);

    /**
     * Sends error information. This can be user related error or server error.
     * Cancellation error is AuthenticationCancelError.
     *
     * @param exc return {@link Exception}
     */
    void onError(Exception exc);
}
// New Interface for Interactive AcquireToken
public interface AuthenticationCallback {

    /**
     * Authentication finishes successfully.
     *
     * @param authenticationResult {@link IAuthenticationResult} that contains the success response.
     */
    void onSuccess(final IAuthenticationResult authenticationResult);

    /**
     * Error occurs during the authentication.
     *
     * @param exception The {@link MsalException} contains the error code, error message and cause if applicable. The exception
     *                  returned in the callback could be {@link MsalClientException}, {@link MsalServiceException}
     */
    void onError(final MsalException exception);

    /**
     * Will be called if user cancels the flow.
     */
    void onCancel();
}

// New Interface for Silent AcquireToken
public interface SilentAuthenticationCallback {

    /**
     * Authentication finishes successfully.
     *
     * @param authenticationResult {@link IAuthenticationResult} that contains the success response.
     */
    void onSuccess(final IAuthenticationResult authenticationResult);

    /**
     * Error occurs during the authentication.
     *
     * @param exception The {@link MsalException} contains the error code, error message and cause if applicable. The exception
     *                  returned in the callback could be {@link MsalClientException}, {@link MsalServiceException} or
     *                  {@link MsalUiRequiredException}.
     */
    void onError(final MsalException exception);
}

迁移到新的异常

在 ADAL 中,有一种异常类型,即 AuthenticationException,它包含一个用于获取 ADALError 枚举值的方法。 在 MSAL 中,存在异常层次结构,每个异常都有其自己的关联特定错误代码集。

Exception Description
MsalArgumentException 如果一个或多个输入参数无效,则引发。
MsalClientException 如果错误发生在客户端,则会引发异常。
MsalDeclinedScopeException 如果服务器拒绝了一个或多个请求的范围,则引发。
MsalException MSAL 抛出的默认受检异常。
MsalIntuneAppProtectionPolicyRequiredException 如果资源已启用 MAMCA 保护策略,则引发此策略。
MsalServiceException 如果错误来自服务器端,则会引发此错误。
MsalUiRequiredException 如果令牌无法以无提示方式刷新,则引发。
MsalUserCancelException 如果用户取消了身份验证流,则引发。

ADALError 到 MsalException 的转换

如果你在 ADAL 中遇到这些错误... ...捕获以下 MSAL 异常:
没有对应的 ADALError MsalArgumentException
  • ADALError.ANDROIDKEYSTORE_FAILED
  • ADALError.AUTH_FAILED_USER_MISMATCH
  • ADALError.DECRYPTION_FAILED
  • ADALError.DEVELOPER_AUTHORITY_CAN_NOT_BE_VALIDED
  • ADALError.DEVELOPER_AUTHORITY_IS_NOT_VALID_INSTANCE
  • ADALError.DEVELOPER_AUTHORITY_IS_NOT_VALID_URL
  • ADALError.DEVICE_CONNECTION_IS_NOT_AVAILABLE
  • ADALError.DEVICE_NO_SUCH_ALGORITHM
  • ADALError.ENCODING_IS_NOT_SUPPORTED
  • ADALError.ENCRYPTION_ERROR
  • ADALError.IO_EXCEPTION
  • ADALError.JSON_PARSE_ERROR
  • ADALError.NO_NETWORK_CONNECTION_POWER_OPTIMIZATION
  • ADALError.SOCKET_TIMEOUT_EXCEPTION
MsalClientException
没有对应的 ADALError MsalDeclinedScopeException
  • ADALError.APP_PACKAGE_NAME_NOT_FOUND
  • ADALError.BROKER_APP_VERIFICATION_FAILED
  • ADALError.PACKAGE_NAME_NOT_FOUND
MsalException
没有对应的 ADALError MsalIntuneAppProtectionPolicyRequiredException
  • ADALError.SERVER_ERROR
  • ADALError.SERVER_INVALID_REQUEST
MsalServiceException
  • ADALError.AUTH_REFRESH_FAILED_PROMPT_NOT_ALLOWED
MsalUiRequiredException
没有对应的 ADALError MsalUserCancelException

ADAL 日志记录迁移到 MSAL 日志记录

// Legacy Interface
    StringBuilder logs = new StringBuilder();
    Logger.getInstance().setExternalLogger(new ILogger() {
            @Override
            public void Log(String tag, String message, String additionalMessage, LogLevel logLevel, ADALError errorCode) {
                logs.append(message).append('\n');
            }
        });
// New interface
  StringBuilder logs = new StringBuilder();
  Logger.getInstance().setExternalLogger(new ILoggerCallback() {
      @Override
      public void log(String tag, Logger.LogLevel logLevel, String message, boolean containsPII) {
          logs.append(message).append('\n');
      }
  });

// New Log Levels:
public enum LogLevel
{
    /**
     * Error level logging.
     */
    ERROR,
    /**
     * Warning level logging.
     */
    WARNING,
    /**
     * Info level logging.
     */
    INFO,
    /**
     * Verbose level logging.
     */
    VERBOSE
}