如何为Office 365脚本仅授予自身邮箱的访问权限?
解决方案:仅访问自身邮箱的Graph API邮件附件下载脚本
问题分析
你当前使用的是客户端凭证授权流(Client Credentials Flow),这种模式下的Mail.Read属于应用权限,默认需要管理员同意,且会授予访问所有邮箱的权限——这就是你遇到Access is denied错误的核心原因。而Graph Explorer使用的是授权码流(Authorization Code Flow),属于委派权限,仅需你个人登录同意即可访问自身邮箱,但临时令牌无法长期复用。
调整方案:改用带刷新令牌的授权码流
要实现长期、安全地访问自身邮箱,且无需管理员同意,需切换到授权码流并启用令牌缓存(自动用刷新令牌续期访问令牌)。
步骤1:Azure AD应用配置
- 注册应用时,设置重定向URI为
http://localhost:8000(或本地可访问的地址) - 添加委派权限:
Mail.Read(注意是「委派权限」而非「应用权限」),该权限无需管理员同意,你个人登录即可授权
步骤2:修改后的可运行代码
import msal import json import requests import os # 配置参数 CLIENT_ID = '你的客户端ID' TENANT_ID = '你的租户ID' REDIRECT_URI = 'http://localhost:8000' SCOPE = ['https://graph.microsoft.com/Mail.Read'] # 令牌缓存文件,用于持久化刷新令牌 TOKEN_CACHE_FILE = 'token_cache.json' def load_token_cache(): if os.path.exists(TOKEN_CACHE_FILE): with open(TOKEN_CACHE_FILE, 'r') as f: return msal.SerializableTokenCache(json.load(f)) return msal.SerializableTokenCache() def save_token_cache(cache): with open(TOKEN_CACHE_FILE, 'w') as f: json.dump(cache.serialize(), f) def get_access_token(): cache = load_token_cache() app = msal.PublicClientApplication( client_id=CLIENT_ID, authority=f'https://login.microsoftonline.com/{TENANT_ID}', token_cache=cache ) # 优先从缓存获取有效令牌 accounts = app.get_accounts() if accounts: result = app.acquire_token_silent(SCOPE, account=accounts[0]) if result: save_token_cache(cache) return result # 无有效令牌时,触发浏览器登录授权 result = app.acquire_token_interactive(SCOPE, redirect_uri=REDIRECT_URI) if 'access_token' in result: save_token_cache(cache) return result else: raise Exception(f"令牌获取失败: {result.get('error_description')}") # 获取访问令牌 token_result = get_access_token() access_token = token_result['access_token'] # 邮件筛选条件(注意日期格式需带时区) date_received = '20**-**-**T00:00:00Z' mail_subject = '目标邮件主题' mail_sender = 'sender@example.com' # 使用/me端点访问当前登录用户的邮箱(无需指定用户ID) url = f"https://graph.microsoft.com/v1.0/me/messages?$filter=startswith(from/emailAddress/address, '{mail_sender}') and subject eq '{mail_subject}' and receivedDateTime ge {date_received}" headers = { "Authorization": f"Bearer {access_token}", "Content-Type": "application/json" } # 获取符合条件的邮件列表 response = requests.get(url, headers=headers) response.raise_for_status() data = response.json() # 下载邮件附件 def get_email_attachment(message_id): url = f"https://graph.microsoft.com/v1.0/me/messages/{message_id}/attachments" response = requests.get(url, headers=headers) response.raise_for_status() return response.json() for d in data["value"]: mes_id = d['id'] attachments = get_email_attachment(mes_id) for attachment in attachments['value']: attachment_name = attachment['name'] attachment_id = attachment['id'] print(f"正在下载: {attachment_name}") # 附件下载端点需追加/$value获取二进制内容 download_url = f"https://graph.microsoft.com/v1.0/me/messages/{mes_id}/attachments/{attachment_id}/$value" response = requests.get(download_url, headers=headers) response.raise_for_status() # 保存附件到本地 with open(attachment_name, 'wb') as f: f.write(response.content) print(f"{attachment_name} 保存完成")
关键说明
- 授权码流优势:第一次运行会弹出浏览器让你登录授权,后续运行自动读取缓存的刷新令牌续期访问令牌,无需重复登录
- 权限范围控制:委派权限
Mail.Read仅允许访问当前登录用户的邮箱,不会触及其他用户数据,无需管理员审批 - 端点优化:用
/me代替/users/{mail_user},自动关联当前登录用户,简化代码 - 日期格式要求:Graph API的
receivedDateTime参数必须带时区(如2024-05-01T00:00:00Z),否则会触发格式错误
内容的提问来源于stack exchange,提问作者Arton
相关产品推荐
相关产品推荐

