使用具有 XOAUTH2 和 MS365 应用程序权限的 IMAP 来对用户进行 IMAP 身份验证

Tho*_*ard 8 python imap oauth-2.0 azure-ad-msal

我有一个较旧的 Python 项目,它使用标准 IMAP 机制从邮箱检索电子邮件等进行处理。不幸的是,随着 MS365 现在淘汰非 OAuth2 和非现代身份验证,我必须尝试编写一个不依赖用户凭据但可以像使用 OAuth2 的其他用户一样完全访问身份验证等的应用程序。

我已经获取了 MSAL 库部分,并且可以从远程获取访问令牌 - 该应用程序配置了使用客户端密钥登录的工作流,并且可以访问所有用户的所有 EWS 以及应用程序权限中的所有 IMAP.UseAsApp。不过,我可能通过应用程序集成错误地请求了信息。

该应用程序正在使用 Azure AD 中分配给它的以下权限进行操作:

在此输入图像描述

所述应用程序通过共享秘密而不是证书进行身份验证。

我们正在拉取 Outlook 范围,因为我们想要使用 Office 365 Exchange Online 的 IMAP 范围,并通过此令牌和 oauth 使用具有 IMAP 身份验证的内容,而且我不相信 MIcrosoft Graph API 具有任何可用的 IMAP 身份验证端点机制,

下面基本上是我尝试将 MSAL OAuth2 与 Azure AD 中配置的应用程序链接起来以获得工作调用的示例imap.authenticate,至少弄清楚如何使用不记名令牌完成 OAuth2 部分:

import imaplib
import msal
import pprint
import base64

conf = {
    "authority": "https://login.microsoftonline.com/TENANT_ID",
    "client_id": "APP_CLIENT_ID",
    "scope": ["https://outlook.office.com/.default"],
    "secret": "APP_SECRET_KEY",
    "secret-id": "APP_SECRET_KEY (for documentation purposes)",
    "tenant-id": "TENANT_ID"
}


def generate_auth_string(user, token):
    authstr = f"user={user}\x01auth=Bearer {token}".encode('utf-8')
    return base64.b64encode(authstr)


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['scopes'])
    
    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 time!
    imap = imaplib.IMAP4('outlook.office365.com')
    imap.starttls()
    imap.authenticate("XOAUTH2", lambda x: generate_auth_string('example@example.com',
                                                                result['access_token']))
    
    # more IMAP stuff after this, but i haven't gotten further than this.
Run Code Online (Sandbox Code Playgroud)

AUTHENTICATE failed每次我使用它来访问有效用户的帐户时,我都会收到一条消息AUTHENTICATE。之所以必须作为应用程序而不是通过委托用户身份验证来完成此操作,是因为它是一个无头 Python 应用程序,需要通过 IMAP 访问大量收件箱(以拉取 RFC822 格式消息),而不仅仅是一个特定的邮箱,我们希望不必为单个用户生成单独的 OAuth 令牌,我们宁愿只在应用程序级别拥有它。

有人知道我在这里做错了什么吗?或者,如果有人能给我指出正确的方向,找到一个可行的例子,那就太好了。

Ami*_*mit 7

尝试以下步骤,因为它对我有用。

\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-MailboxPermission 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, authority=authority, \n          client_credential = clientSecret)\n    access_token = app.acquire_token_for_client(scopes=scope)\n    return access_token\n\n def 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

  • 您好,注册您的应用程序并添加权限“Office 365 Exchange Online / IMAP.AccessAsApp”(应用程序)后,请参阅此链接https://office365itpros.com/2022/07/04/exchange-online-imap4-pop3/? (2认同)

小智 2

我正在尝试做类似的事情。以下更改对我有用:

def generate_auth_string(user, token):
    return f"user={user}\x01auth=Bearer {token}\x01\x01"
Run Code Online (Sandbox Code Playgroud)