如何在CloudFoundry应用中通过Python调用Graph API用AZAD账号发邮件
问题描述
我今天在Python里调试OAuth和MSAL相关功能时碰到了问题。我开发了一个简单应用,用户提交表单后数据会写入数据库,现在想通过我的AZAD系统账号发邮件通知用户数据已入库。
我只想用用户名和密码获取令牌来调用Graph API发邮件,但尝试过程中遇到阻碍。我拿不到应用的管理员权限,不需要代表任意用户发件,只用我的系统账号就行——这个账号在桌面Outlook里能手动发邮件,但在Python IDE里实现不了。我希望尽量通过HTTP请求完成操作。
以下是我尝试的代码:
import requests_oauthlib from requests_oauthlib import OAuth2Session from oauthlib.oauth2 import LegacyApplicationClient tenant_id = tid client_id = cid client_secret = cs uri =f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token" username=un password=pw #DOES NOT WORK says the user account doesn't exist oauth = OAuth2Session(client=LegacyApplicationClient(client_id=client_id)) token = oauth.fetch_token( token_url=uri ,client_id=client_id ,client_secret=client_secret ,username=username ,password=password ,scope="Mail.Send" ) #THIS DOES WORK BUT I CANT GET IT TO EMAIL client = BackendApplicationClient(client_id=client_id) oauth = OAuth2Session(client=client) token = oauth.fetch_token( token_url=uri ,client_id=client_id ,client_secret=client_secret ,scope=["https://graph.microsoft.com/.default"] ) recipient='my_email@mycompany.com' subject="test_subject" body="test_body" request_body = { 'message': { # recipient list 'toRecipients': [ { 'emailAddress': { 'address': recipient } } ], # email subject 'subject': subject, "body": { "contentType": "html", "content": body }, 'importance': 'normal', } } headers = { 'Authorization': 'Bearer ' + token['access_token'] } GRAPH_ENDPOINT = 'https://graph.microsoft.com/v1.0' endpoint = GRAPH_ENDPOINT + '/me/sendMail' response = requests.post(endpoint, headers=headers, json=request_body) response.raise_for_status() # Raise an exception if request fails if response.status_code == 202: print(f"Email sent to: {recipient}") else: print(f"Email not sent to: {recipient}")
问题分析与解决方案
第一种方法(ROPC模式)失败原因
你用的LegacyApplicationClient对应OAuth2的资源所有者密码凭证授权(ROPC),这种模式在Azure AD中有明确限制:
- 账号不能启用MFA,否则直接失效
- 账号必须是完整的用户主体名称(UPN,比如
user@tenant.com),不能只用短用户名 - 应用需在Azure AD注册中添加委托权限的
Mail.Send,并完成用户自身的权限同意 - 本地AD同步过来的账号可能需要额外配置,仅云账号兼容性更好
第二种方法(客户端凭证模式)无法发邮件原因
客户端凭证模式拿到的是应用权限,该模式下没有关联的登录用户,因此不能使用/me/sendMail接口(/me代表当前登录用户)。必须指定具体发送邮件的用户邮箱,调用/users/{user-upn}/sendMail接口,同时需要给应用授予应用权限的Mail.Send并获得管理员同意。
适配场景的解决方案
方案1:ROPC模式(优先选择,若账号满足条件)
如果你的系统账号未启用MFA,直接用ROPC模式即可:
- 在Azure AD应用注册中添加委托权限的
Mail.Send,并用你的系统账号完成权限同意 - 确保代码中用户名是完整UPN格式
- 调用
/me/sendMail接口
修正后代码:
import requests from requests_oauthlib import OAuth2Session from oauthlib.oauth2 import LegacyApplicationClient tenant_id = "你的租户ID" client_id = "你的客户端ID" client_secret = "你的客户端密钥" token_url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token" username = "你的系统账号完整UPN(如xxx@company.com)" password = "你的账号密码" # 初始化ROPC模式会话 oauth = OAuth2Session(client=LegacyApplicationClient(client_id=client_id)) token = oauth.fetch_token( token_url=token_url, client_id=client_id, client_secret=client_secret, username=username, password=password, scope="Mail.Send" ) # 邮件请求参数 recipient = "user@example.com" subject = "数据入库通知" body = "你的表单数据已成功写入数据库。" request_body = { "message": { "toRecipients": [{"emailAddress": {"address": recipient}}], "subject": subject, "body": {"contentType": "html", "content": body}, "importance": "normal" } } headers = {"Authorization": f"Bearer {token['access_token']}"} graph_endpoint = "https://graph.microsoft.com/v1.0" response = requests.post(f"{graph_endpoint}/me/sendMail", headers=headers, json=request_body) response.raise_for_status() print(f"邮件已发送至: {recipient}" if response.status_code == 202 else f"邮件发送失败: {recipient}")
方案2:客户端凭证模式(ROPC不可用时)
如果系统账号启用了MFA,改用客户端凭证模式:
- 在Azure AD应用注册中添加应用权限的
Mail.Send,并让管理员完成权限同意 - 将接口从
/me/sendMail改为/users/{system-account-upn}/sendMail
修正后代码:
import requests from requests_oauthlib import OAuth2Session from oauthlib.oauth2 import BackendApplicationClient tenant_id = "你的租户ID" client_id = "你的客户端ID" client_secret = "你的客户端密钥" token_url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token" system_account_upn = "你的系统账号完整UPN(如xxx@company.com)" # 初始化客户端凭证模式会话 client = BackendApplicationClient(client_id=client_id) oauth = OAuth2Session(client=client) token = oauth.fetch_token( token_url=token_url, client_id=client_id, client_secret=client_secret, scope=["https://graph.microsoft.com/.default"] ) # 邮件请求参数 recipient = "user@example.com" subject = "数据入库通知" body = "你的表单数据已成功写入数据库。" request_body = { "message": { "toRecipients": [{"emailAddress": {"address": recipient}}], "subject": subject, "body": {"contentType": "html", "content": body}, "importance": "normal" } } headers = {"Authorization": f"Bearer {token['access_token']}"} graph_endpoint = "https://graph.microsoft.com/v1.0" # 调用指定用户的发邮件接口 response = requests.post(f"{graph_endpoint}/users/{system_account_upn}/sendMail", headers=headers, json=request_body) response.raise_for_status() print(f"邮件已发送至: {recipient}" if response.status_code == 202 else f"邮件发送失败: {recipient}")
内容的提问来源于stack exchange,提问作者rrz
相关产品推荐
相关产品推荐

