403 Authorization_RequestDenied 尝试读取用户时

403 Authorization_RequestDenied when trying to read users

我正在尝试使用 API 端点 https://graph.microsoft.com/v1.0/users/ 获取 Exchange 目录中所有用户的列表 这使用 Graph Explorer 有效,我得到了包含 100 个有效结果列表的预览。

然而,当从我的 Python 应用程序访问同一个端点时,以同一个用户登录,我收到以下响应:

{'error': {'code': 'Authorization_RequestDenied', 'message': 'Insufficient privileges to complete the operation.', 'innerError': {'request-id': '6924dba1-83ee-4865-9dcf-76b6cafcb808', 'date': '2019-07-04T21:08:28'}}}

这是我正在使用的 Python 代码,其中大部分是从 python-sample-console-app

复制的
CLIENT_ID = '765bdd8d-b33d-489b-a039-3cdf41223aa4'

AUTHORITY_URL = 'https://login.microsoftonline.com/common'
RESOURCE = 'https://graph.microsoft.com'
API_VERSION = 'v1.0'

import base64
import mimetypes
import os
import urllib
import webbrowser

from adal import AuthenticationContext
import pyperclip
import requests

def device_flow_session(client_id, auto=False):
    """Obtain an access token from Azure AD (via device flow) and create
    a Requests session instance ready to make authenticated calls to
    Microsoft Graph.
    client_id = Application ID for registered "Azure AD only" V1-endpoint app
    auto      = whether to copy device code to clipboard and auto-launch browser
    Returns Requests session object if user signed in successfully. The session
    includes the access token in an Authorization header.
    User identity must be an organizational account (ADAL does not support MSAs).
    """
    ctx = AuthenticationContext(AUTHORITY_URL, api_version=None)
    device_code = ctx.acquire_user_code(RESOURCE, client_id)

    # display user instructions
    if auto:
        pyperclip.copy(device_code['user_code']) # copy user code to clipboard
        webbrowser.open(device_code['verification_url']) # open browser
        print(f'The code {device_code["user_code"]} has been copied to your clipboard, '
              f'and your web browser is opening {device_code["verification_url"]}. '
              'Paste the code to sign in.')
    else:
        print(device_code['message'])

    token_response = ctx.acquire_token_with_device_code(RESOURCE,
                                                        device_code,
                                                        client_id)
    if not token_response.get('accessToken', None):
        return None

    session = requests.Session()
    session.headers.update({'Authorization': f'Bearer {token_response["accessToken"]}',
                            'SdkVersion': 'sample-python-adal',
                            'x-client-SKU': 'sample-python-adal'})
    return session

def api_endpoint(url):
    """Convert a relative path such as /me/photo/$value to a full URI based
    on the current RESOURCE and API_VERSION settings in config.py.
    """
    if urllib.parse.urlparse(url).scheme in ['http', 'https']:
        return url # url is already complete
    return urllib.parse.urljoin(f'{RESOURCE}/{API_VERSION}/',
url.lstrip('/'))

session = device_flow_session(CLIENT_ID, True)
users = session.get(api_endpoint('users'))
print(users.json())

我还在 Microsoft Azure 门户中设置了以下权限:

具体来说 User.ReadBasic.All 应该足以获取所有用户的列表,仅具有姓名和电子邮件等基本属性,但它仍然不起作用。

显然,缺少问题 "user consent"。这里有些奇怪,因为理论上登录页面应该自动请求用户同意所有配置的 API 权限。经过一些修改,我找到了以下解决方案:

解决方案 1

解决方案 2

CONSENT_URL = f'https://login.microsoftonline.com/common/oauth2/authorize?client_id={CLIENT_ID}&response_type=code&response_mode=query&resource=https://graph.microsoft.com&state=12345&prompt=consent'
#...
response = session.get(api_endpoint('users')).json()
if 'error' in response and response['error']['code'] == 'Authorization_RequestDenied':
    print("Access denied. Consent page has been opened in the webbrowser. Please give user consent, then start the script again!")
    webbrowser.open(CONSENT_URL)
    sys.exit(1)

当您第一次使用图形资源管理器时,您必须征得同意才能使用该资源管理器。

因此,当您使用在 Azure 门户中注册的应用程序登录时,您也必须获得使用该应用程序的同意。但是如果你add/update同意之后的权限。您必须再次同意。

您可以通过 url 请求强制用户同意,将 &prompt=consent 附加到身份验证请求 URL。

或者您只是代表此目录中的所有用户同意。为所有用户授予管理员同意意味着最终用户在使用该应用程序时不会看到同意屏幕。