通过 OAuth2 和 python MSAL 库进行 Office 365 IMAP 身份验证

qui*_*tin 21 python email imap oauth-2.0 office365

我正在尝试升级旧版邮件机器人以通过 Oauth2 而不是基本身份验证进行身份验证,因为它现在已在两天后被弃用

该文档指出应用程序可以保留其原始逻辑,同时仅交换身份验证位

使用这些协议构建发送、阅读或以其他方式处理电子邮件的应用程序的应用程序开发人员将能够保留相同的协议,但需要为其用户实现安全、现代的身份验证体验。此功能构建在 Microsoft Identity 平台 v2.0 之上,支持访问 Microsoft 365 电子邮件帐户。

注意我已经明确选择了客户端凭据流程,因为文档指出

这种类型的授权通常用于必须在后台运行的服务器到服务器交互,而不需要立即与用户交互。

因此,我有一个 python 脚本,可以使用MSAL python 库检索访问令牌。现在我尝试使用该访问令牌向 IMAP 服务器进行身份验证。有一些现有的线程展示了如何连接到 Google,我想我的情况与非常接近,只是我正在连接到 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)
Run Code Online (Sandbox Code Playgroud)

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.
Run Code Online (Sandbox Code Playgroud)

知道我可能出错的地方,或者如何从 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
Run Code Online (Sandbox Code Playgroud)
  • 该线程似乎建议需要获取多个令牌,一个用于图形,然后另一个用于 IMAP 连接;这可能是我所缺少的吗?

Ami*_*mit 8

尝试以下步骤。

\n

对于客户端凭据流程,您需要在应用程序注册中分配 \xe2\x80\x9cApplication 权限\xe2\x80\x9d,而不是 \xe2\x80\x9cDelegate 权限\xe2\x80\x9d。

\n
    \n
  1. 添加权限 \xe2\x80\x9cOffice 365 Exchange Online / IMAP.AccessAsApp\xe2\x80\x9d (应用程序)。\n在此输入图像描述
  2. \n
  3. 授予管理员同意您的申请
  4. \n
  5. 服务主体和交流。
  6. \n
  7. 在 Exchange Online 中注册服务主体后,管理员可以运行 Add-Mailbox Permission cmdlet 为服务主体分配接收权限。
  8. \n
  9. 使用范围“https://outlook.office365.com/.default\”。
  10. \n
\n

现在,您可以通过组合此访问令牌和邮箱用户名来生成 SALS 身份验证字符串,以使用 IMAP4 进行身份验证。

\n

#Python代码

\n
def get_access_token():\n    tenantID = \'abc\'\n    authority = \'https://login.microsoftonline.com/\' + tenantID\n    clientID = \'abc\'\n    clientSecret = \'abc\'\n    scope = [\'https://outlook.office365.com/.default\']\n    app = ConfidentialClientApplication(clientID, \n          authority=authority, \n          client_credential = clientSecret)\n    access_token = app.acquire_token_for_client(scopes=scope)\n    return access_token\n\ndef generate_auth_string(user, token):\n    auth_string = f"user={user}\\1auth=Bearer {token}\\1\\1"\n    return auth_string\n\n#IMAP AUTHENTICATE\n imap = imaplib.IMAP4_SSL(imap_host, 993)\n imap.debug = 4\n access_token = get_access_token_to_authenticate_imap()\n imap.authenticate("XOAUTH2", lambda x:generate_auth_string(\n      \'useremail\',\n       access_token[\'access_token\']))\n imap.select(\'inbox\')\n
Run Code Online (Sandbox Code Playgroud)\n

  • 您的答案中正确的一件事是范围,我将其更改为“https://outlook.office365.com/.default”。正如您所建议的,我们还分配了“应用程序权限”。最终,MS 方面发生了变化。这与 SPN 的对象 ID 有关。当您进行注册时,将创建企业应用程序,并且需要使用企业应用程序中的对象 ID。 (2认同)
  • 嘿@henry434,我有一些来自 MS 的回顾笔记。需要获得客户的许可才能在此处共享,但我想他们会同意的,我将在接下来的几天内添加带有注释的答案。抱歉耽搁了。 (2认同)

Sar*_*jli 6

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

通过 Powershell 设置服务主体时,您需要输入应用程序 ID 和对象 ID。很多人会认为,就是你在注册App的概览页面看到的Object-ID,其实不是!此时,您需要来自“Azure Active Directory -> Enterprise Applications --> Your-App --> Object-ID”的对象 ID

New-ServicePrincipal -AppId <APPLICATION_ID> -ServiceId <OBJECT_ID> [-Organization <ORGANIZATION_ID>]
Run Code Online (Sandbox Code Playgroud)

微软说:

OBJECT_ID 是用于应用程序注册的企业应用程序节点(Azure 门户)的概述页面中的对象 ID。它不是“应用程序注册概述”节点中的对象 ID。使用不正确的对象 ID 将导致身份验证失败。

当然,您需要注意 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"))
Run Code Online (Sandbox Code Playgroud)

设置服务主体并授予应用程序对邮箱的完全访问权限后,等待 15 - 30 分钟以使更改生效并进行测试。

  • 很高兴听到你成功了。让我与我的 MS 专家再次确认,如果他们同意这是一个公平的规范答案,我将接受并为您提供适度的赏金。 (2认同)