获取令牌

可通过多种方式通过 MSAL Python获取令牌。 有些需要用户交互,而另一些则不需要用户交互。 用于获取令牌的方法有所不同,具体取决于开发人员是构建公共客户端(桌面或移动客户端)还是机密客户端应用程序(Web 应用、Web API 或守护程序(如Windows服务)。

先决条件

在使用 MSAL Python获取令牌之前,请了解客户端应用程序的类型

获取用户帐户

应用可以自行获取令牌,也可以代表用户获取令牌。 若要代表用户获取令牌,应用需要知道用户的帐户。 MSAL Python提供了get_accounts获取用户帐户的方法。 该方法在 PublicClientApplication 类和 ConfidentialClientApplication 类中均可用。 该方法返回用户以前登录的帐户列表,即缓存中存在。

accounts = app.get_accounts(username=user.get("preferred_username"))

用户选择用于登录的账户之后可在 acquire_token_silent() 中用于查找该账户的令牌。

令牌授予流

有几个身份验证流可用于通过 MSAL Python获取令牌。 你可以在Microsoft 标识平台文档中找到有关这些流的更多信息。

Warning

始终使用 MSAL 获取安全令牌并在应用中调用受保护的 Web API。 我们不建议您自行实现令牌获取逻辑。 这些流有助于更好地了解工作原理。 如果要保护 Web 应用程序,我们建议使用 标识 库。 此库不是由Microsoft正式维护的,但它实现了在 Web 应用中获取令牌所需的大部分逻辑。

交互式与静默

MSAL Python 同时支持交互式获取令牌和静默获取令牌。 交互式令牌获取需要用户交互,而无提示令牌获取则不需要。 公共客户端通常需要用户交互,而机密客户端依赖于预先预配的凭据,例如证书和机密。

使用 acquire_token_silent_with_error 方法以静默方式获取令牌。 此方法从缓存中查找有效的访问令牌,或缓存中的有效刷新令牌,然后自动使用它来兑换新的访问令牌。 如果两者都不是真的,则需要使用交互式方法来获取令牌。

如果应用在查找令牌缓存时不关心具体的令牌刷新错误,则建议使用 acquire_token_silent 方法。

此方法的示例用法如以下代码片段所示。

if accounts:
    # If so, you could then somehow display these accounts and let end user choose
    chosen = accounts[0]
    result = app.acquire_token_silent(scopes=["your_scope"], account=chosen)
    
    # At this point, you can save you can update your cache if you are using token caching
    # check result variable, if its None then you should interactively acquire a token
    if not result:
        # So no suitable token exists in cache. Let's get a new one from Microsoft Entra.
        result = app.acquire_token_by_one_of_the_actual_method(..., scopes=["User.Read"])
    
    if "access_token" in result:
        access_token = result["access_token"]
    else:
        print(result.get("error"))  
        print(result.get("error_description"))
        print(result.get("correlation_id"))  # You may need this when reporting a bug

有多种方法可用于交互式令牌获取。 要使用的方法取决于要构建的应用的类型以及适用于你的方案的令牌授予流。

公共客户端交互式获取令牌

公共客户端应用程序无法安全地存储机密,并且只能对与产品交互的用户进行身份验证。 MSAL Python通过PublicClientApplication公开公共应用程序的令牌获取逻辑。 以下是可用于公共客户端应用程序获取令牌的不同方法。

设备代码流

设备代码流 用于获取在无法访问 Web 浏览器的设备上运行的应用程序中的令牌。 这些应用程序被称为无头应用程序。 此流为用户提供 URL 和代码。 用户转到另一台设备上的 Web 浏览器,输入代码并登录。 身份验证成功后,Microsoft Entra 会向无浏览器的设备返回令牌。

首先,调用该方法 initiate_device_flow

flow = app.initiate_device_flow(scopes=config["scope"])
if "user_code" not in flow:
    raise ValueError(
        "Fail to create device flow. Err: %s" % json.dumps(flow, indent=4))

print(flow["message"])
sys.stdout.flush()  # Some terminal needs this to ensure the message is shown

# Ideally you should wait here, in order to save some unnecessary polling
# input("Press Enter after signing in from another device to proceed, CTRL+C to abort.")

然后将流字典对象传递给 acquire_token_by_device_flow 方法以获取令牌。 默认情况下,此方法会阻止当前线程。 可以按照 这些说明 缩短阻止时间,甚至可以关闭阻止行为,然后在自己的自定义循环中继续调用 acquire_token_by_device_flow

result = app.acquire_token_by_device_flow(flow)

if "access_token" in result:
    access_token = result["access_token"]
else:
    print(result.get("error"))  

成功的响应是一个包含 access_token 键的字典。

交互式获取令牌

MSAL Python还提供公共客户端应用(桌面和移动版)以用户身份获取令牌的功能。 用户通过 Web 浏览器通过授权请求 URL 登录。 在 Microsoft Entra 管理中心的应用注册中,将应用的重定向 URI 设置为 http://localhost。 如果你在 PublicClientApplication 创建期间选择使用代理,你的应用还需要将 ms-appx-web://Microsoft.AAD.BrokerPlugin/YOUR_CLIENT_ID 注册为重定向 URI。

result = app.acquire_token_interactive(  # It automatically provides PKCE protection
    scopes=config["scope"])

if "access_token" in result:
    access_token = result["access_token"]
else:
    print(result.get("error"))  

用户名和密码

Warning

由于安全风险,此 API 已弃用公共客户端流,请使用更安全的流。 请遵循 本指南 获取迁移指南。

不建议使用此方法。 还可以使用 用户名和密码获取令牌。 MSAL Python 提供了适用于此用例的 acquire_token_by_username_password 方法。 不建议这样做,因为应用程序会直接要求用户输入其密码,这是不安全模式。

你可以使用更安全的流程。 在 用户名和密码身份验证流 指南中了解详细信息。

result = app.acquire_token_by_username_password(
    username=config["username"], password=config["password"], scopes=config["scope"])

if "access_token" in result:
    access_token = result["access_token"]
else:
    print(result.get("error"))  

机密客户端交互式获取令牌

机密客户端应用程序可以安全地存储机密,并且可以代表应用程序以及代表给定用户进行身份验证。 MSAL Python为开发人员提供了在开发ConfidentialClientApplication时获取令牌的各种方法。

为客户端获取令牌

使用 客户端凭据 以应用程序自身身份获取令牌,而非代表用户。 例如,这可用于以批处理方式处理用户而不是一个特定用户(例如同步工具)的应用程序。 MSAL Python提供了acquire_token_for_client执行此操作的方法。 自 MSAL Python 1.23 版本起,此方法会自动先从缓存中查找令牌,并且仅在缓存未命中时才向身份提供程序发送请求。

result = app.acquire_token_for_client(scopes=config["scope"])

if "access_token" in result:
    access_token = result["access_token"]
else:
    print(result.get("error"))    

代表 获取令牌

如果 Web 应用或 Web API 以用户身份调用另一个下游 Web API,请使用 “代表用户流” 基于用户断言获取令牌。 例如,SAML 和 JWT。 当前应用是使用表示最终用户的令牌调用的中间层服务。 当前应用可以使用此类令牌(也称为用户断言)请求另一个令牌代表该用户访问下游 Web API。 中间层应用没有用户交互来获得同意。 有关提前获得中间层应用的同意的信息,请参阅 文档

下面是使用 acquire_token_on_behalf_of 该方法获取访问令牌的代码示例。

def get(self, request): # a web service endpoint receiving a request
    
    scopes = ["your-scopes"]
    downstream_api = "https://your-downstreamapi.com/resource" #your downstream API resource endpoint
    current_access_token = request.headers.get("Authorization", None)
    
    # initialize the app
    app = msal.ConfidentialClientApplication(...) # refer to initialization of the app documentation

    #acquire token on behalf of the user that called this API
    downstream_api_access_token = app.acquire_token_on_behalf_of(
        user_assertion=current_app_access_token.split(' ')[1],
        scopes=_scopes
    )

    if "access_token" in result:
        access_token = result["access_token"]
        # use access_token to call dowstream API e.g
        requests.get(downstream_api, headers={'Authorization': f'Bearer {downstream_api_access_token}'})
    else:
        print(result.get("error")) 

通过授权代码流获取令牌

对于以用户名称进行身份验证的 Web 应用,在允许用户通过授权请求 URL 登录后,通过 授权代码 获取令牌。 这通常是应用程序使用的机制,它允许用户登录和访问此特定用户的 Web API。

你首先需要使用 initiate_auth_code_flow 启动授权代码流程。 此方法采用重定向 URI 和状态字符串等参数。 状态参数的值也包含在令牌响应中。 如果此值不存在,MSAL Python将在内部自动生成一个。 提供的重定向 URI 必须与在Microsoft Entra 管理中心中注册的重定向 URI 匹配。 此方法返回授权码流程,该流程是一个包含 auth_uristate 的字典。 auth_uri 是用户登录时需要访问的 URL。

flow = app.initiate_auth_code_flow(
    scopes=config["scope"], redirect_uri=config["redirect_uri"], state="your-state-value")

if "error" in flow:
    print(flow.get("error"))

# Save the response somewhere e.g in session
session["auth_flow"] = flow

# At this point, the app should guide the user to visit the auth ur (session["auth_flow"]["auth_uri"])

访问身份验证 URI 端点获得的响应会在 acquire_token_by_auth_code_flow 方法中使用。 状态是一个唯一标识符,可用于验证授权服务器的响应。 用户应在登录时同意相应的权限范围。

# The uth_response value from visiting the auth_uri endpoint is passed as a query string
# You can change this by passing a value to the response_mode in the initiate_auth_code_flow method
try:
    result = app.acquire_token_by_auth_code_flow(session.get("flow", {}), auth_response)
    
    if "access_token" in result:
        access_token = result["access_token"]
    else:
        print(result.get("error"))
except ValueError:  # Usually caused by CSRF
    pass  # Simply ignore them

MSAL Python令牌缓存

公共和机密客户端应用程序都支持由 MSAL Python直接处理的令牌缓存。 应用程序应先尝试从缓存获取令牌,然后再依赖任何其他方法。 有关详细信息,请参阅 建议的令牌获取模式

为了能够持久保存缓存,开发人员需要配置 令牌缓存序列化 逻辑。