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
- 在 portal.azure.com
上注册一个新的应用程序
- 预先添加所需的配置和 API 权限
- 当您更改所需的 API 权限时,用户 不会再次被询问
- 每当您需要访问新的 API 并相应地更改应用程序中的应用程序 ID 时,请在 portal.azure.com 上重新创建应用程序
解决方案 2
- 检测到 'Authorization_RequestDenied' 错误
- 如果发生这种情况,请打开浏览器 window 使用特殊登录名 URL 征求用户同意
- 要求用户在成功登录并同意后重启应用程序
- 这里是相关代码
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。
或者您只是代表此目录中的所有用户同意。为所有用户授予管理员同意意味着最终用户在使用该应用程序时不会看到同意屏幕。
我正在尝试使用 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
- 在 portal.azure.com 上注册一个新的应用程序
- 预先添加所需的配置和 API 权限
- 当您更改所需的 API 权限时,用户 不会再次被询问
- 每当您需要访问新的 API 并相应地更改应用程序中的应用程序 ID 时,请在 portal.azure.com 上重新创建应用程序
解决方案 2
- 检测到 'Authorization_RequestDenied' 错误
- 如果发生这种情况,请打开浏览器 window 使用特殊登录名 URL 征求用户同意
- 要求用户在成功登录并同意后重启应用程序
- 这里是相关代码
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。
或者您只是代表此目录中的所有用户同意。为所有用户授予管理员同意意味着最终用户在使用该应用程序时不会看到同意屏幕。