Microsoft Graph API:为具有应用程序权限的多租户应用程序获取 ErrorAccessDenied

Microsoft Graph API: Getting ErrorAccessDenied for multi-tenant application with application permission

我正在为使用 outlook 的客户(多个租户)编写一个守护程序应用程序。

我正在使用 2 个需要管理员同意的应用程序权限 - Mail.ReadBasic.All User.Read.All。我的应用程序首先需要读取所有用户的 ID,然后获取他们电子邮件的所有元数据。

我用 office365 创建了一个新租户来测试这个,我们称之为测试,并在 2 个用户之间发送了几封电子邮件。
因此,首先,我将测试组织的管理员重定向到 adminconsent 端点,其中 he/she 正在向我的应用程序授予应用程序权限。这是我正在使用的URL:

https://login.microsoftonline.com/organizations/v2.0/adminconsent?
client_id=<the app ID>
&state=<some state>
&redirect_uri=<my redirect URL as written in the app configuration>
&scope=https://graph.microsoft.com/.default

调用此端点后,我可以看到我的应用程序列在企业应用程序下的测试组织中,并且可以看到管理员授予了相关权限。

由于我没有从该流程中获取代码(oAuth2 身份验证流程需要),因此我需要让管理员再次登录。我为此使用 URL:

https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize?
client_id=<same app ID>
&response_type=code
&redirect_uri=<same redirect URL>
&scope=https://graph.microsoft.com/.default+offline_access+openid+profile
&state=<some state>

登录成功后,我得到一个代码返回到我的重定向 URL,在另一个请求之后,我得到一个访问令牌。使用此访问令牌,我正在尝试访问以下任何 API:

但我收到 ErrorAccessDenied 消息:Access is denied. Check credentials and try again.

更多信息: 我正在使用 python 和 MSAL 包来构建应用程序(使用 class - ConfidentialClientApplication)和用于身份验证流程的 URLs(但不适用于 adminconsent端点,因为我不知道该怎么做)

你知道我做错了什么吗?我为此失去了理智...... :(

此页面应描述您需要的一切: https://docs.microsoft.com/graph/auth-v2-service

管理员许可 URL 应特定于客户的租户。如果您想允许登录任何租户,可以使用 common 一词。

https://login.microsoftonline.com/{tenant}/adminconsent

您还必须 URL 对 redirect_uri 参数(以及所有其他参数)进行编码。由于某种原因,该文档中的示例未进行 URL 编码,但此处的值必须进行 URL 编码。您应该看不到此参数的冒号、斜线、和号等。

有关请求管理员同意的特定范围的不同示例(而不是默认范围,即您在 AAD 客户端应用程序注册期间列出的所有范围),请参阅 https://docs.microsoft.com/azure/active-directory/develop/v2-admin-consent

  1. 您将收到重定向 URI 的回调,表明一切正常。这包括授予您管理员许可的租户 ID。

  2. 之后,您为租户 ID、您的应用程序客户端 ID 和特定的请求范围启动单独的令牌请求调用。然后,这将 return 一个适当范围的访问令牌,您可以在所有 API 调用中直接使用它。你可以这样做:https://docs.microsoft.com/azure/active-directory/develop/scenario-daemon-acquire-token?tabs=python#acquiretokenforclient-api

# The pattern to acquire a token looks like this.
result = None

# First, the code looks up a token from the cache.
# Because we're looking for a token for the current app, not for a user,
# use None for the account parameter.
result = app.acquire_token_silent(config["scope"], account=None)

if not result:
    logging.info("No suitable token exists in cache. Let's get a new one from AAD.")
    result = app.acquire_token_for_client(scopes=config["scope"])

if "access_token" in result:
    # Call a protected API with the access token below.
    print(result["token_type"])
else:
    print(result.get("error"))
    print(result.get("error_description"))
    print(result.get("correlation_id"))  # You might need this when reporting a bug.

希望对您有所帮助。上面的文章有所有的细节。