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

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

您可以详细了解 MSAL,并通过Python 版 Microsoft Authentication Library 概述开始上手。

差异亮点

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

支持:

  • 工作和学校帐户(Microsoft Entra ID预配帐户)

  • 个人帐户(如 Outlook.com 或 Hotmail.com)

  • 通过 Azure AD B2C 产品/服务使用自己的电子邮件或社交身份(如 领英、Facebook、Google)的客户

  • 是否兼容相关标准:

    • OAuth v2.0
    • OpenID Connect (OIDC)

有关 MSAL 的详细信息,请参阅 MSAL 概述

范围不是资源

ADAL Python 获取针对资源的令牌,而 MSAL Python 获取针对作用域的令牌。 MSAL Python中的 API 图面不再具有资源参数。 需要提供范围作为字符串列表,这些字符串声明所需的权限和资源。 若要查看作用域的一些示例,请参阅Microsoft Graph的范围

可以向资源添加 /.default 作用域后缀,以帮助将应用从 v1.0 终结点(ADAL)迁移到 Microsoft 标识平台(MSAL)。 例如,对于资源值 https://graph.microsoft.com,等效的范围值为 https://graph.microsoft.com/.default。 如果资源不是 URL 形式,而是采用 XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXX 这种形式的资源 ID,则仍然可以将 scope 值设为 XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXX/.default

有关不同类型的作用域的详细信息,请参阅Microsoft 标识平台中的权限和许可以及接受 v1.0 令牌的 Web API 的作用域这两篇文章。

错误处理

Python ADAL 使用异常AdalError来指示存在问题。 而适用于 Python 的 MSAL 通常使用错误代码。 有关详细信息,请参阅 适用于 Python 的 MSAL 错误处理

API 变动

下表列出了 Python 版 ADAL 中的 API,以及 Python 版 MSAL 中可替代它的 API:

适用于 Python API 的 ADAL 适用于 Python API 的 MSAL
AuthenticationContext PublicClientApplicationConfidentialClientApplication
N/A acquire_token_interactive
N/A get_authorization_request_url
N/A initiate_auth_code_flow
acquire_token_with_authorization_code() acquire_token_by_auth_code_flow
acquire_token() acquire_token_silent
acquire_token_with_refresh_token() 这两个帮助程序仅用于 迁移 期间: acquire_token_by_refresh_token
acquire_user_code() initiate_device_flow
acquire_token_with_device_code()cancel_request_to_get_token_with_device_code() acquire_token_by_device_flow
acquire_token_with_username_password() acquire_token_by_username_password
acquire_token_with_client_credentials()acquire_token_with_client_certificate() acquire_token_for_client
N/A acquire_token_on_behalf_of
TokenCache() SerializableTokenCache
N/A 具有持久性的缓存,可从 MSAL 扩展获取

迁移 MSAL Python 的现有刷新令牌

MSAL 提取出刷新令牌的概念。 MSAL Python默认提供内存中令牌缓存,因此无需存储、查找或更新刷新令牌。 用户还会看到更少的登录提示,因为刷新令牌通常可以更新,而无需用户干预。 有关令牌缓存的详细信息,请参阅 MSAL 中用于Python的自定义令牌缓存序列化

以下代码将帮助你把当前由其他 OAuth2 库(包括但不限于 ADAL Python)管理的刷新令牌迁移为由 Python 版 MSAL 管理。 迁移这些刷新令牌的原因之一,是为了在将应用迁移到适用于 Python 的 MSAL 时,避免现有用户需要再次登录。

迁移刷新令牌的方法是使用适用于 Python 的 MSAL,并利用先前的刷新令牌获取新的访问令牌。 返回新的刷新令牌后,Python的 MSAL 会将其存储在缓存中。 由于 MSAL Python 1.3.0,因此我们在 MSAL 中为此提供了 API。 请参阅以下代码片段,该代码片段引用了使用 MSAL Python 迁移刷新令牌的完整示例

import msal
def get_preexisting_rt_and_their_scopes_from_elsewhere():
    # Maybe you have an ADAL-powered app like this
    #   https://github.com/AzureAD/azure-activedirectory-library-for-python/blob/1.2.3/sample/device_code_sample.py#L72
    # which uses a resource rather than a scope,
    # you need to convert your v1 resource into v2 scopes
    # See https://learn.microsoft.com/azure/active-directory/develop/migrate-python-adal-msal#scopes-not-resources
    # You may be able to append "/.default" to your v1 resource to form a scope
    # See https://learn.microsoft.com/azure/active-directory/develop/v2-permissions-and-consent#the-default-scope

    # Or maybe you have an app already talking to the Microsoft identity platform,
    # powered by some 3rd-party auth library, and persist its tokens somehow.

    # Either way, you need to extract RTs from there, and return them like this.
    return [
        ("old_rt_1", ["scope1", "scope2"]),
        ("old_rt_2", ["scope3", "scope4"]),
        ]


# We will migrate all the old RTs into a new app powered by MSAL
app = msal.PublicClientApplication(
    "client_id", authority="...",
    # token_cache=...  # Default cache is in memory only.
                       # You can learn how to use SerializableTokenCache from
                       # https://msal-python.readthedocs.io/en/latest/#msal.SerializableTokenCache
    )

# We choose a migration strategy of migrating all RTs in one loop
for old_rt, scopes in get_preexisting_rt_and_their_scopes_from_elsewhere():
    result = app.acquire_token_by_refresh_token(old_rt, scopes)
    if "error" in result:
        print("Discarding unsuccessful RT. Error: ", json.dumps(result, indent=2))

print("Migration completed")