使用Python和Graph API从Outlook发件遇权限拒绝问题求助
问题分析与解决方案
核心错误原因
你当前使用的acquire_token_for_client是客户端凭证授权流,该流程仅支持应用级权限,但你配置的是委托权限(且组织不允许应用级权限),两者不匹配导致权限拒绝错误。委托权限要求必须以具体用户身份完成认证,而非应用本身直接获取权限。
解决方案
根据你的场景(无人值守脚本、组织限制委托权限),提供两种可行的授权流方案:
方案1:用户名密码授权流(无MFA场景)
适用于用户账号未开启多因素认证的情况,直接通过用户名密码获取委托权限的token:
import msal import requests import json # 替换为你的配置信息 TENANT_ID = 'Tenant ID' AUTHORITY_URL = f'https://login.microsoftonline.com/{TENANT_ID}' CLIENT_ID = 'Client ID' USERNAME = "你的Outlook邮箱地址" PASSWORD = "你的邮箱密码" SCOPES = ["Mail.Send"] # 指定具体的委托权限 # 初始化公共客户端应用(委托权限使用公共客户端) app = msal.PublicClientApplication( client_id=CLIENT_ID, authority=AUTHORITY_URL ) # 获取token result = app.acquire_token_by_username_password( username=USERNAME, password=PASSWORD, scopes=SCOPES ) if "access_token" in result: # 使用/me端点代表当前授权用户发送邮件 endpoint = 'https://graph.microsoft.com/v1.0/me/sendMail' toUserEmail = "收件人邮箱地址" email_msg = { 'Message': { 'Subject': "脚本运行报错通知", 'Body': {'ContentType': 'Text', 'Content': "报表数据收集脚本出现异常,请检查SharePoint文件状态。"}, 'ToRecipients': [{'EmailAddress': {'Address': toUserEmail}}] }, 'SaveToSentItems': 'true' } r = requests.post( endpoint, headers={'Authorization': 'Bearer ' + result['access_token']}, json=email_msg ) if r.ok: print('报错邮件发送成功') else: print(r.json()) else: print(f"获取token失败: {result.get('error')} - {result.get('error_description')}")
注意:
- 请勿硬编码密码,建议通过环境变量或密钥管理工具存储。
- 若账号开启MFA,此方案无法使用,需改用设备授权流。
方案2:设备授权流(支持MFA场景)
无需存储密码,首次运行需手动在浏览器完成身份验证(支持MFA),后续脚本可自动刷新token,适合长期无人值守运行:
import msal import requests import json # 替换为你的配置信息 TENANT_ID = 'Tenant ID' AUTHORITY_URL = f'https://login.microsoftonline.com/{TENANT_ID}' CLIENT_ID = 'Client ID' SCOPES = ["Mail.Send"] # 初始化公共客户端应用 app = msal.PublicClientApplication( client_id=CLIENT_ID, authority=AUTHORITY_URL ) # 启动设备授权流程 flow = app.initiate_device_flow(scopes=SCOPES) if "user_code" not in flow: raise ValueError(f"设备流程初始化失败: {json.dumps(flow, indent=4)}") # 输出验证提示 print(flow["message"]) # 轮询获取token result = app.acquire_token_by_device_flow(flow) if "access_token" in result: endpoint = 'https://graph.microsoft.com/v1.0/me/sendMail' toUserEmail = "收件人邮箱地址" email_msg = { 'Message': { 'Subject': "脚本运行报错通知", 'Body': {'ContentType': 'Text', 'Content': "报表数据收集脚本出现异常,请检查SharePoint文件状态。"}, 'ToRecipients': [{'EmailAddress': {'Address': toUserEmail}}] }, 'SaveToSentItems': 'true' } r = requests.post( endpoint, headers={'Authorization': 'Bearer ' + result['access_token']}, json=email_msg ) if r.ok: print('报错邮件发送成功') else: print(r.json()) else: print(f"获取token失败: {result.get('error')} - {result.get('error_description')}")
注意:
- 首次运行会输出一个验证链接和代码,需在浏览器打开链接并输入代码完成登录授权,MSAL会自动缓存token,后续运行无需再次验证。
- 需在Entra ID应用注册中,将应用类型设置为移动和桌面应用,并添加重定向URI
http://localhost(路径:应用注册 -> 身份验证 -> 添加平台 -> 移动和桌面应用)。
关于权限审批的问题
Mail.Send属于无需管理员同意的委托权限(除非组织修改了默认权限策略),使用上述两种委托权限流时,普通用户自身即可完成授权,不需要管理员审批。之前遇到的审批要求,可能是因为误用了需要管理员同意的权限或应用配置错误。
内容的提问来源于stack exchange,提问作者Merlin Nestler
相关产品推荐
相关产品推荐

