You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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模式即可:

  1. 在Azure AD应用注册中添加委托权限的Mail.Send,并用你的系统账号完成权限同意
  2. 确保代码中用户名是完整UPN格式
  3. 调用/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,改用客户端凭证模式:

  1. 在Azure AD应用注册中添加应用权限的Mail.Send,并让管理员完成权限同意
  2. 将接口从/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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 05:34:58