You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用Graph API委托权限发送邮件时持续触发401 Unauthorized错误的排查求助

Graph API委托权限发送邮件时持续触发401 Unauthorized错误的排查求助

我在Azure上创建了一个应用,想用Graph API的委托权限来实现邮件发送功能,目前采用交互式流程,通过oauth2/v2.0/authorizeoauth2/v2.0/token端点获取授权码和访问令牌,应用注册的配置如下:

应用注册配置界面

以下是我实现整个流程的Python代码:

import requests
import webbrowser
import urllib.parse

class GraphEmailSender:
    def __init__(self, client_id, tenant_id):
        """
        Initialize Graph Email Sender
        
        :param client_id: Azure AD application client ID
        :param tenant_id: Azure AD tenant ID
        """
        self.client_id = client_id
        self.tenant_id = tenant_id
        
        # OAuth endpoints
        self.authorize_url = f'https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/authorize'
        self.token_url = f'https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token'
        
        # Graph API endpoint
        self.graph_endpoint = 'https://graph.microsoft.com/v1.0'
        
        # Redirect URI (must be registered in Azure AD app)
        self.redirect_uri = 'http://localhost:8000/callback'
        
        # Scopes for email sending
        self.scopes = [
            'https://graph.microsoft.com/Mail.Send',
            'https://graph.microsoft.com/User.Read',
            'offline_access'
        ]
    
    def get_authorization_code(self):
        """
        Generate authorization URL and prompt for manual code entry
        
        :return: Authorization code
        """
        # Prepare authorization request parameters
        auth_params = {
            'client_id': self.client_id,
            'response_type': 'code',
            'redirect_uri': self.redirect_uri,
            'scope': ' '.join(self.scopes),
            'response_mode':'fragment'
        }
        
        # Construct authorization URL
        auth_url = f"{self.authorize_url}?{urllib.parse.urlencode(auth_params)}"
        
        # Open browser for authorization
        print("Please authorize the application:")
        webbrowser.open(auth_url)
        
        # Manually enter authorization code
        auth_code = input("Enter the authorization code from the redirect URL: ")
        return auth_code
    
    def exchange_code_for_token(self, authorization_code):
        """
        Manually exchange authorization code for access token
        
        :param authorization_code: Authorization code
        :return: Access token
        """
        # Prepare token exchange parameters
        token_params = {
            'client_id': self.client_id,
            'grant_type': 'authorization_code',
            'code': authorization_code,
            'redirect_uri': self.redirect_uri,
            'scope': ' '.join(self.scopes),
            'client_secret':'Ixxxx'
        }
        
        # Send token request
        response = requests.post(
            self.token_url, 
            data=token_params,
            headers={'Content-Type': 'application/x-www-form-urlencoded'}
        )
        # Return access token
        return response.json().get('access_token')
    
    def send_email(self, access_token, to_email, subject, body):
        """
        Send email using access token
        
        :param access_token: OAuth access token
        :param to_email: Recipient email address
        :param subject: Email subject
        :param body: Email body
        :return: Boolean indicating success
        """
        # Prepare email message
        email_message = {
            "message": {
                "subject": subject,
                "body": {
                    "contentType": "Text",
                    "content": body
                },
                "toRecipients": [
                    {
                        "emailAddress": {
                            "address": to_email
                        }
                    }
                ],
                "from":{
                    "emailAddresss":{
                        "address":"lxxx1@gmail.com"
                    }
                }
            },
            "saveToSentItems": "true"
        }
        
        # Prepare headers
        headers = {
            'Authorization': f'Bearer {access_token}',
            'Content-Type': 'application/json'
        }
        try:
            # Send email via Graph API
            response = requests.post(
                f'{self.graph_endpoint}/me/sendMail', 
                json=email_message, 
                headers=headers
            )
            
            # Check response
            if response.status_code in [200, 201, 202]:
                print("Email sent successfully!")
                return True
            else:
                print(f"Failed to send email. Status code: {response.status_code} {response}")
                return False
        
        except Exception as e:
            print(f"Error sending email: {e}")
            return False

# Example usage
def main():
    # Replace with your actual Azure AD application details
    TENANT_ID = '97sss'
    CLIENT_ID = '803csss'
    
    # Create email sender
    email_sender = GraphEmailSender(CLIENT_ID, TENANT_ID)
    
    # Get authorization code (manual process)
    auth_code = email_sender.get_authorization_code()
    
    # Exchange code for access token
    access_token = email_sender.exchange_code_for_token(auth_code)
    
    # Send email
    email_sender.send_email(
        access_token,
        to_email='gagsgdg@gmail.com',
        subject='OAuth Email Test',
        body='Email sent using simplified OAuth flow.'
    )

if __name__ == '__main__':
    main()

我检查了生成的访问令牌,确认其中已经包含了所需的权限(Mail.SendUser.Read),JWT解析后的结果如下:

JWT权限验证截图

但调用/me/sendMail接口时,始终返回401 Unauthorized,错误信息为:

Failed to send email. Status code: 401 <Response [401]>

我已经在Azure应用中配置了正确的重定向URI,并且将支持的账户类型设置为“所有账户”:

应用权限配置截图

补充完整的错误输出截图:

错误输出截图

现在有个疑问:是不是必须使用Office365账户才能通过这种方式发送邮件?还是任何微软账户(比如个人Outlook/Hotmail账户)都可以?另外有没有其他配置或代码问题导致了这个401错误?麻烦各位帮忙排查一下,非常感谢!

备注:内容来源于stack exchange,提问作者Lucifer Darknight

火山引擎 最新活动