通过OAuth2和python MSAL库进行的Office 365 IMAP认证

回答 3 浏览 5513 2022-09-29

我正在尝试升级一个传统的邮件机器人,以便通过Oauth2而不是Basic认证进行认证,因为它是从现在起两天后讲被弃用

该文件指出,应用程序可以保留其原来的逻辑,而只换掉认证位。

Application developers who have built apps that send, read, or otherwise process email using these protocols will be able to keep the same protocol, but need to implement secure, Modern authentication experiences for their users. This functionality is built on top of Microsoft Identity platform v2.0 and supports access to Microsoft 365 email accounts.

注意我明确地选择了客户证书流程,因为文档中说到

This type of grant is commonly used for server-to-server interactions that must run in the background, without immediate interaction with a user.

所以我有一个python脚本,使用MSAL python库检索一个访问令牌。现在我正试图使用该访问令牌与IMAP服务器进行验证。我想我的情况和这个很接近,只是我连接的是Office 365的IMAP服务器。下面是我的脚本

import imaplib
import msal
import logging

app = msal.ConfidentialClientApplication(
    'client-id',
    authority='https://login.microsoftonline.com/tenant-id',
    client_credential='secret-key'
)

result = app.acquire_token_for_client(scopes=['https://graph.microsoft.com/.default'])

def generate_auth_string(user, token):
  return 'user=%s\1auth=Bearer %s\1\1' % (user, token)

# IMAP time!
mailserver = 'outlook.office365.com'
imapport = 993
M = imaplib.IMAP4_SSL(mailserver,imapport)
M.debug = 4
M.authenticate('XOAUTH2', lambda x: generate_auth_string('user@mydomain.com', result['access_token']))

print(result)

IMAP认证失败了,尽管设置了M.debug = 4,但输出结果并不是很有帮助。

  22:56.53 > b'DBDH1 AUTHENTICATE XOAUTH2'
  22:56.53 < b'+ '
  22:56.53 write literal size 2048
  22:57.84 < b'DBDH1 NO AUTHENTICATE failed.'
  22:57.84 NO response: b'AUTHENTICATE failed.'
Traceback (most recent call last):
  File "/home/ubuntu/mini-oauth.py", line 21, in <module>
    M.authenticate("XOAUTH2", lambda x: generate_auth_string('user@mydomain.com', result['access_token']))
  File "/usr/lib/python3.10/imaplib.py", line 444, in authenticate
    raise self.error(dat[-1].decode('utf-8', 'replace'))
imaplib.IMAP4.error: AUTHENTICATE failed.

有什么想法,我可能在哪里出了问题,或者如何从IMAP服务器上获得更多关于认证失败原因的有力信息?

我所看的东西

import base64

user = 'test@contoso.onmicrosoft.com'
token = 'EwBAAl3BAAUFFpUAo7J3Ve0bjLBWZWCclRC3EoAA'

xoauth = "user=%s\1auth=Bearer %s\1\1" % (user, token)

xoauth = xoauth.encode('ascii')
xoauth = base64.b64encode(xoauth)
xoauth = xoauth.decode('ascii')

xsanity = 'dXNlcj10ZXN0QGNvbnRvc28ub25taWNyb3NvZnQuY29tAWF1dGg9QmVhcmVyIEV3QkFBbDNCQUFVRkZwVUFvN0ozVmUwYmpMQldaV0NjbFJDM0VvQUEBAQ=='

print(xoauth == xsanity) # prints True
  • 这个主题似乎表明需要获取多个令牌,一个用于图形,另一个用于IMAP连接;这可能是我所遗漏的吗?
quickshiftin 提问于2022-09-29
我有同样的问题。还有一个关于这个问题的问题。同样的错误:"AUTHENTICATE failed",在尝试使用IMAP和你之前得到的令牌进行认证后。如果你找到解决办法,请发表出来。Sardar Agabejli 2022-10-03
3 个回答
#1楼
得票数 7

试试下面的步骤。

对于客户凭证流,你需要在应用注册中指定 "应用权限",而不是 "委托权限"。

  1. 添加权限 "Office 365 Exchange Online / IMAP.AccessAsApp"(应用程序)。 在此输入图片描述
  2. 授予管理员对您的申请的同意权
  3. 服务原则和交流。
  4. 一旦服务委托人在Exchange Online上注册,管理员就可以运行Add-Mailbox Permission cmdlet来为服务委托人分配接收权限。
  5. 使用范围'https://outlook.office365.com/.default'。

现在你可以通过结合这个访问令牌和邮箱的用户名来生成SALS认证字符串,以便用IMAP4进行身份验证。

#Python的代码

def get_access_token():
    tenantID = 'abc'
    authority = 'https://login.microsoftonline.com/' + tenantID
    clientID = 'abc'
    clientSecret = 'abc'
    scope = ['https://outlook.office365.com/.default']
    app = ConfidentialClientApplication(clientID, 
          authority=authority, 
          client_credential = clientSecret)
    access_token = app.acquire_token_for_client(scopes=scope)
    return access_token

def generate_auth_string(user, token):
    auth_string = f"user={user}\1auth=Bearer {token}\1\1"
    return auth_string

#IMAP AUTHENTICATE
 imap = imaplib.IMAP4_SSL(imap_host, 993)
 imap.debug = 4
 access_token = get_access_token_to_authenticate_imap()
 imap.authenticate("XOAUTH2", lambda x:generate_auth_string(
      'useremail',
       access_token['access_token']))
 imap.select('inbox')
Amit 提问于2022-10-12
Ben Cottrell 修改于2022-12-05
你的答案中有一点是正确的,那就是范围,我把它改为https://outlook.office365.com/.default。我们还分配了"应用权限",正如你所建议的。最后是这个问题和MS方面的一个变化。这是与SPN的对象标识有关的问题。当你进行注册时,企业应用程序被创建,企业应用程序的对象标识需要被使用。quickshiftin 2022-10-12
@quickshiftin 你好,很高兴听到你把这个问题解决了。你能不能就你评论的后半部分再展开一些?从"最后是那个和MS方面的变化......等等"开始,我想不出来需要做什么。谢谢!henry434 2022-10-13
嘿,@henry434,我有一些来自MS的总结性笔记。需要得到客户的许可才能在这里分享,但我想他们会同意的,我将在接下来的几天里把答案和笔记一起加入。对不起,耽搁了。quickshiftin 2022-10-14
我也开了一张MS Ticket,现在他们告诉我,这是一个第三方的问题(imaplib)。我对此很不满意。你能不能告诉我你的MS案例编号,这样我就可以告诉他们看一下?请分享答案/解决方案。谢谢你Sardar Agabejli 2022-10-18
@quickshiftin 谢谢你对Object-ID的提示,经过检查,它现在对我来说是有效的!我已经分享了答案。:)Sardar Agabejli 2022-10-19
#2楼 已采纳
得票数 2

imaplib.IMAP4.error: AUTHENTICATE failed 错误的发生是因为文档中的一个点不是那么清楚。

在通过Powershell设置服务主体时,你需要输入App-ID和一个Object-ID。很多人会认为,这就是你在已注册的App的概览页面上看到的Object-ID,但事实并非如此!此时,你需要从"Azure Active Directory --> Enterprise Applications --> Your-App --> Object-ID"获得Object-ID。

New-ServicePrincipal -AppId <APPLICATION_ID> -ServiceId <OBJECT_ID> [-Organization <ORGANIZATION_ID>]

微软公司说:

The OBJECT_ID is the Object ID from the Overview page of the Enterprise Application node (Azure Portal) for the application registration. It is not the Object ID from the Overview of the App Registrations node. Using the incorrect Object ID will cause an authentication failure.

当然,你需要注意API权限和其他东西,但对我来说,这才是重点。 所以,让我们再看一遍,就像文档页上所解释的那样。 使用OAuth认证IMAP、POP或SMTP连接

  1. 在你的租户中登记申请
  2. 为应用程序设置一个客户端密钥。
  3. 设置API权限,选择我的组织使用的API标签,并搜索"Office 365 Exchange Online"->应用权限->选择IMAP和IMAP.AccessAsApp
  4. 在邮箱上为你的应用程序设置服务主体和完整的访问权限。
  5. 检查邮箱的IMAP是否已被激活

这是我用来测试的代码:

import imaplib
import msal
import pprint

conf = {
    "authority": "https://login.microsoftonline.com/XXXXyourtenantIDXXXXX",
    "client_id": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXX", #AppID
    "scope": ['https://outlook.office365.com/.default'],
    "secret": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", #Key-Value
    "secret-id": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", #Key-ID
}
    
def generate_auth_string(user, token):
    return f"user={user}\x01auth=Bearer {token}\x01\x01"    

if __name__ == "__main__":
    app = msal.ConfidentialClientApplication(conf['client_id'], authority=conf['authority'],
                                             client_credential=conf['secret'])

    result = app.acquire_token_silent(conf['scope'], account=None)

    if not result:
        print("No suitable token in cache.  Get new one.")
        result = app.acquire_token_for_client(scopes=conf['scope'])

    if "access_token" in result:
        print(result['token_type'])
        pprint.pprint(result)
    else:
        print(result.get("error"))
        print(result.get("error_description"))
        print(result.get("correlation_id"))
        
    imap = imaplib.IMAP4('outlook.office365.com')
    imap.starttls()
    imap.authenticate("XOAUTH2", lambda x: generate_auth_string("target_mailbox@example.com", result['access_token']).encode("utf-8"))

在设置了服务委托人并给予应用程序在邮箱上的完全访问权后,等待15-30分钟,让变化生效,然后进行测试。

Sardar Agabejli 提问于2022-10-19
很高兴听到你得到了它的工作。让我和我的MS大师们再确认一下,如果他们同意这是一个公平的标准答案,我将接受并为你提供适度的赏金。quickshiftin 2022-10-20
#3楼
得票数 0

用这个脚本试试吧:

import json
import msal

import requests

client_id = '***'
client_secret = '***'
tenant_id = '***'
authority = f"https://login.microsoftonline.com/{tenant_id}"

app = msal.ConfidentialClientApplication(
    client_id=client_id,
    client_credential=client_secret,
    authority=authority)

scopes = ["https://graph.microsoft.com/.default"]

result = None
result = app.acquire_token_silent(scopes, account=None)

if not result:
    print(
        "No suitable token exists in cache. Let's get a new one from Azure Active Directory.")
    result = app.acquire_token_for_client(scopes=scopes)

# if "access_token" in result:
#     print("Access token is " + result["access_token"])


if "access_token" in result:
    userId = "***"
    endpoint = f'https://graph.microsoft.com/v1.0/users/{userId}/sendMail'
    toUserEmail = "***"
    email_msg = {'Message': {'Subject': "Test Sending Email from Python",
                             'Body': {'ContentType': 'Text', 'Content': "This is a test email."},
                             'ToRecipients': [{'EmailAddress': {'Address': toUserEmail}}]
                             },
                 'SaveToSentItems': 'true'}
    r = requests.post(endpoint,
                      headers={'Authorization': 'Bearer ' + result['access_token']}, json=email_msg)
    if r.ok:
        print('Sent email successfully')
    else:
        print(r.json())
else:
    print(result.get("error"))
    print(result.get("error_description"))
    print(result.get("correlation_id"))

来源:https://kontext.tech/article/795/python-send-email-via-microsoft-graph-api

jemutorres 提问于2022-09-30
谢谢你的帖子,但有两个问题--1.我需要使用我现有的所有逻辑与IMAP库,我不想重写整个代码,只需要认证。2.这是对SMTP的调用,我的问题是关于IMAP。quickshiftin 2022-09-30