用于 IMAP 和 SMTP 身份验证的 Office 365 XOAUTH2 失败

led*_*iov 6 java imap smtp oauth-2.0 office365

最近宣布支持 Exchange Online 中的 IMAP 和 SMTP 的 OAuth 2.0。按照指南,我设置了应用程序权限以及 IMAP 和 SMTP 连接。应用程序配置为Accounts in any organizational directory (Any Azure AD directory - Multitenant)并使用授权代码流

以下 URL 用于授权:

并且添加了以下委派 Microsoft Graph 范围:

在此处输入图片说明

范围,来自代码的请求:

final List<String> scopes = Arrays.asList(
    "offline_access",
    "email",
    "openid",
    "profile",
    "User.Read",
    "Mail.ReadWrite",
    "https%3A%2F%2Foutlook.office365.com%2FIMAP.AccessAsUser.All",
    "https%3A%2F%2Foutlook.office365.com%2FSMTP.Send"
);
Run Code Online (Sandbox Code Playgroud)

我成功收到访问和刷新令牌:

{
    "token_type": "Bearer",
    "scope": "email IMAP.AccessAsUser.All Mail.ReadWrite openid profile SMTP.Send User.Read",
    "expires_in": 3599,
    "ext_expires_in": 3599,
    "access_token": "edited",
    "refresh_token": "edited",
    "id_token": "edited"
}
Run Code Online (Sandbox Code Playgroud)

这是用于连接到 IMAP 的代码:

Properties props = new Properties();
props.put("mail.imap.ssl.enable", "true");
props.put("mail.imaps.sasl.enable", "true");
props.put("mail.imaps.sasl.mechanisms", "XOAUTH2");
props.put("mail.imap.auth.login.disable", "true");
props.put("mail.imap.auth.plain.disable", "true");
props.put("mail.debug", "true");
props.put("mail.debug.auth", "true");

Session session = Session.getInstance(props);
session.setDebug(true);

String userEmail = "user@domain.onmicrosoft.com";
String accessToken = "access_token_received_on_previous_step";

final Store store = session.getStore("imaps");
store.connect("outlook.office365.com", 993, userEmail, accessToken);
Run Code Online (Sandbox Code Playgroud)

这会生成以下输出:

DEBUG: JavaMail version 1.6.2
DEBUG: successfully loaded resource: /META-INF/javamail.default.address.map
DEBUG: setDebug: JavaMail version 1.6.2
DEBUG: getProvider() returning javax.mail.Provider[STORE,imaps,com.sun.mail.imap.IMAPSSLStore,Oracle]
DEBUG IMAPS: mail.imap.fetchsize: 16384
DEBUG IMAPS: mail.imap.ignorebodystructuresize: false
DEBUG IMAPS: mail.imap.statuscachetimeout: 1000
DEBUG IMAPS: mail.imap.appendbuffersize: -1
DEBUG IMAPS: mail.imap.minidletime: 10
DEBUG IMAPS: enable SASL
DEBUG IMAPS: SASL mechanisms allowed: XOAUTH2
DEBUG IMAPS: closeFoldersOnStoreFailure
DEBUG IMAPS: trying to connect to host "outlook.office365.com", port 993, isSSL true
* OK The Microsoft Exchange IMAP4 service is ready. [QQBNADc...]
A0 CAPABILITY
* CAPABILITY IMAP4 IMAP4rev1 AUTH=PLAIN AUTH=XOAUTH2 SASL-IR UIDPLUS MOVE ID UNSELECT CHILDREN IDLE NAMESPACE LITERAL+
A0 OK CAPABILITY completed.
DEBUG IMAPS: AUTH: PLAIN
DEBUG IMAPS: AUTH: XOAUTH2
DEBUG IMAPS: protocolConnect login, host=outlook.office365.com, user=user@domain.onmicrosoft.com, password=<non-null>
DEBUG IMAPS: SASL Mechanisms:
DEBUG IMAPS:  XOAUTH2
DEBUG IMAPS: 
DEBUG IMAPS: SASL client XOAUTH2
DEBUG IMAPS: SASL callback length: 2
DEBUG IMAPS: SASL callback 0: javax.security.auth.callback.NameCallback@17046283
DEBUG IMAPS: SASL callback 1: javax.security.auth.callback.PasswordCallback@5bd03f44
A1 AUTHENTICATE XOAUTH2 dXNlcj1o...
A1 NO AUTHENTICATE failed.

Exception in thread "main" javax.mail.AuthenticationFailedException: AUTHENTICATE failed.
    at com.sun.mail.imap.IMAPStore.protocolConnect(IMAPStore.java:732)
    at javax.mail.Service.connect(Service.java:366)
Run Code Online (Sandbox Code Playgroud)

以下代码用于连接到 SMTP:

DEBUG: JavaMail version 1.6.2
DEBUG: successfully loaded resource: /META-INF/javamail.default.address.map
DEBUG: setDebug: JavaMail version 1.6.2
DEBUG: getProvider() returning javax.mail.Provider[STORE,imaps,com.sun.mail.imap.IMAPSSLStore,Oracle]
DEBUG IMAPS: mail.imap.fetchsize: 16384
DEBUG IMAPS: mail.imap.ignorebodystructuresize: false
DEBUG IMAPS: mail.imap.statuscachetimeout: 1000
DEBUG IMAPS: mail.imap.appendbuffersize: -1
DEBUG IMAPS: mail.imap.minidletime: 10
DEBUG IMAPS: enable SASL
DEBUG IMAPS: SASL mechanisms allowed: XOAUTH2
DEBUG IMAPS: closeFoldersOnStoreFailure
DEBUG IMAPS: trying to connect to host "outlook.office365.com", port 993, isSSL true
* OK The Microsoft Exchange IMAP4 service is ready. [QQBNADc...]
A0 CAPABILITY
* CAPABILITY IMAP4 IMAP4rev1 AUTH=PLAIN AUTH=XOAUTH2 SASL-IR UIDPLUS MOVE ID UNSELECT CHILDREN IDLE NAMESPACE LITERAL+
A0 OK CAPABILITY completed.
DEBUG IMAPS: AUTH: PLAIN
DEBUG IMAPS: AUTH: XOAUTH2
DEBUG IMAPS: protocolConnect login, host=outlook.office365.com, user=user@domain.onmicrosoft.com, password=<non-null>
DEBUG IMAPS: SASL Mechanisms:
DEBUG IMAPS:  XOAUTH2
DEBUG IMAPS: 
DEBUG IMAPS: SASL client XOAUTH2
DEBUG IMAPS: SASL callback length: 2
DEBUG IMAPS: SASL callback 0: javax.security.auth.callback.NameCallback@17046283
DEBUG IMAPS: SASL callback 1: javax.security.auth.callback.PasswordCallback@5bd03f44
A1 AUTHENTICATE XOAUTH2 dXNlcj1o...
A1 NO AUTHENTICATE failed.

Exception in thread "main" javax.mail.AuthenticationFailedException: AUTHENTICATE failed.
    at com.sun.mail.imap.IMAPStore.protocolConnect(IMAPStore.java:732)
    at javax.mail.Service.connect(Service.java:366)
Run Code Online (Sandbox Code Playgroud)

它提供了以下输出:

DEBUG: setDebug: JavaMail version 1.6.2
DEBUG: getProvider() returning javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Oracle]
DEBUG SMTP: useEhlo true, useAuth true
DEBUG SMTP: trying to connect to host "smtp.office365.com", port 587, isSSL false
220 AM5PR0701CA0005.outlook.office365.com Microsoft ESMTP MAIL Service ready at Mon, 4 May 2020 15:52:28 +0000
DEBUG SMTP: connected to host "smtp.office365.com", port: 587
EHLO ubuntu-B450-AORUS-M
250-AM5PR0701CA0005.outlook.office365.com Hello [my ip here]
250-SIZE 157286400
250-PIPELINING
250-DSN
250-ENHANCEDSTATUSCODES
250-STARTTLS
250-8BITMIME
250-BINARYMIME
250-CHUNKING
250 SMTPUTF8
DEBUG SMTP: Found extension "SIZE", arg "157286400"
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "DSN", arg ""
DEBUG SMTP: Found extension "ENHANCEDSTATUSCODES", arg ""
DEBUG SMTP: Found extension "STARTTLS", arg ""
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: Found extension "BINARYMIME", arg ""
DEBUG SMTP: Found extension "CHUNKING", arg ""
DEBUG SMTP: Found extension "SMTPUTF8", arg ""
STARTTLS
220 2.0.0 SMTP server ready
EHLO ubuntu-B450-AORUS-M
250-AM5PR0701CA0005.outlook.office365.com Hello [my ip here]
250-SIZE 157286400
250-PIPELINING
250-DSN
250-ENHANCEDSTATUSCODES
250-AUTH LOGIN XOAUTH2
250-8BITMIME
250-BINARYMIME
250-CHUNKING
250 SMTPUTF8
DEBUG SMTP: Found extension "SIZE", arg "157286400"
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "DSN", arg ""
DEBUG SMTP: Found extension "ENHANCEDSTATUSCODES", arg ""
DEBUG SMTP: Found extension "AUTH", arg "LOGIN XOAUTH2"
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: Found extension "BINARYMIME", arg ""
DEBUG SMTP: Found extension "CHUNKING", arg ""
DEBUG SMTP: Found extension "SMTPUTF8", arg ""
DEBUG SMTP: protocolConnect login, host=smtp.office365.com, user=user@domain.onmicrosoft.com, password=<non-null>
DEBUG SMTP: Attempt to authenticate using mechanisms: XOAUTH2
DEBUG SMTP: Using mechanism XOAUTH2
AUTH XOAUTH2 dXNlcj1obW9kaUB...
535 5.7.3 Authentication unsuccessful [AM5PR0701CA0005.eurprd07.prod.outlook.com]

Exception in thread "main" javax.mail.AuthenticationFailedException: 535 5.7.3 Authentication unsuccessful [AM5PR0701CA0005.eurprd07.prod.outlook.com]
    at com.sun.mail.smtp.SMTPTransport$Authenticator.authenticate(SMTPTransport.java:965)
    at com.sun.mail.smtp.SMTPTransport.authenticate(SMTPTransport.java:876)
    at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:780)
    at javax.mail.Service.connect(Service.java:366)
Run Code Online (Sandbox Code Playgroud)

我也尝试过:

  • 将范围指定为https://graph.microsoft.com/SMTP.Send和只是SMTP.Send
  • 使用https://login.microsoftonline.com/common/url 进行身份验证

结果总是一样的。

是我做错了什么还是微软方面对此的支持存在错误?

更新 1:

从命令行尝试,但结果相同:

$ openssl s_client -crlf -connect outlook.office365.com:993
... connection part omitted
* OK The Microsoft Exchange IMAP4 service is ready. [QQBNADYAUAAxADkAMgBDAEEAMAAwADkAMQAuAEUAVQBSAFAAMQA5ADIALgBQAFIATwBEAC4ATwBVAFQATABPAE8ASwAuAEMATwBNAA==]
C01 CAPABILITY
* CAPABILITY IMAP4 IMAP4rev1 AUTH=PLAIN AUTH=XOAUTH2 SASL-IR UIDPLUS ID UNSELECT CHILDREN IDLE NAMESPACE LITERAL+
C01 OK CAPABILITY completed.
A01 AUTHENTICATE XOAUTH2 dXNlcj1obW9kaUBjb...
A01 NO AUTHENTICATE failed.
* BYE Connection is closed. 13
read:errno=0

Run Code Online (Sandbox Code Playgroud)

更新 2:

尝试使用以下权限在 Azure 门户中创建全新的应用程序:

在此处输入图片说明

并在尝试同意范围时收到以下屏幕:

在此处输入图片说明

这很奇怪,因为 Azure 门户的权限没有指定需要管理员同意,而且我以前的应用程序注册在请求 IMAP 和 SMTP 范围时没有显示这样的屏幕。

更新 3:

感谢对这篇文章的评论,我尝试了以下范围:

Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.auth.mechanisms", "XOAUTH2");
props.put("mail.smtp.auth.login.disable","true");
props.put("mail.smtp.auth.plain.disable","true");
props.put("mail.debug.auth", "true");

Session session = Session.getInstance(props);
session.setDebug(true);

String userEmail = "user@domain.onmicrosoft.com";
String accessToken = "access_token_received_on_previous_step";

Transport transport = session.getTransport("smtp");
transport.connect("smtp.office365.com", 587, userEmail, accessToken);
Run Code Online (Sandbox Code Playgroud)

这给了我下面的令牌:

DEBUG: setDebug: JavaMail version 1.6.2
DEBUG: getProvider() returning javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Oracle]
DEBUG SMTP: useEhlo true, useAuth true
DEBUG SMTP: trying to connect to host "smtp.office365.com", port 587, isSSL false
220 AM5PR0701CA0005.outlook.office365.com Microsoft ESMTP MAIL Service ready at Mon, 4 May 2020 15:52:28 +0000
DEBUG SMTP: connected to host "smtp.office365.com", port: 587
EHLO ubuntu-B450-AORUS-M
250-AM5PR0701CA0005.outlook.office365.com Hello [my ip here]
250-SIZE 157286400
250-PIPELINING
250-DSN
250-ENHANCEDSTATUSCODES
250-STARTTLS
250-8BITMIME
250-BINARYMIME
250-CHUNKING
250 SMTPUTF8
DEBUG SMTP: Found extension "SIZE", arg "157286400"
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "DSN", arg ""
DEBUG SMTP: Found extension "ENHANCEDSTATUSCODES", arg ""
DEBUG SMTP: Found extension "STARTTLS", arg ""
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: Found extension "BINARYMIME", arg ""
DEBUG SMTP: Found extension "CHUNKING", arg ""
DEBUG SMTP: Found extension "SMTPUTF8", arg ""
STARTTLS
220 2.0.0 SMTP server ready
EHLO ubuntu-B450-AORUS-M
250-AM5PR0701CA0005.outlook.office365.com Hello [my ip here]
250-SIZE 157286400
250-PIPELINING
250-DSN
250-ENHANCEDSTATUSCODES
250-AUTH LOGIN XOAUTH2
250-8BITMIME
250-BINARYMIME
250-CHUNKING
250 SMTPUTF8
DEBUG SMTP: Found extension "SIZE", arg "157286400"
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "DSN", arg ""
DEBUG SMTP: Found extension "ENHANCEDSTATUSCODES", arg ""
DEBUG SMTP: Found extension "AUTH", arg "LOGIN XOAUTH2"
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: Found extension "BINARYMIME", arg ""
DEBUG SMTP: Found extension "CHUNKING", arg ""
DEBUG SMTP: Found extension "SMTPUTF8", arg ""
DEBUG SMTP: protocolConnect login, host=smtp.office365.com, user=user@domain.onmicrosoft.com, password=<non-null>
DEBUG SMTP: Attempt to authenticate using mechanisms: XOAUTH2
DEBUG SMTP: Using mechanism XOAUTH2
AUTH XOAUTH2 dXNlcj1obW9kaUB...
535 5.7.3 Authentication unsuccessful [AM5PR0701CA0005.eurprd07.prod.outlook.com]

Exception in thread "main" javax.mail.AuthenticationFailedException: 535 5.7.3 Authentication unsuccessful [AM5PR0701CA0005.eurprd07.prod.outlook.com]
    at com.sun.mail.smtp.SMTPTransport$Authenticator.authenticate(SMTPTransport.java:965)
    at com.sun.mail.smtp.SMTPTransport.authenticate(SMTPTransport.java:876)
    at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:780)
    at javax.mail.Service.connect(Service.java:366)
Run Code Online (Sandbox Code Playgroud)

IMAP/SMTP 身份验证成功,我能够阅读收件箱 + 发送电子邮件!

但是对于我的应用程序,我还需要几个其他范围来使用一些 MS Graph API 端点(读取用户配置文件、消息订阅和消息删除)。

所以我尝试了不同的范围:

$ openssl s_client -crlf -connect outlook.office365.com:993
... connection part omitted
* OK The Microsoft Exchange IMAP4 service is ready. [QQBNADYAUAAxADkAMgBDAEEAMAAwADkAMQAuAEUAVQBSAFAAMQA5ADIALgBQAFIATwBEAC4ATwBVAFQATABPAE8ASwAuAEMATwBNAA==]
C01 CAPABILITY
* CAPABILITY IMAP4 IMAP4rev1 AUTH=PLAIN AUTH=XOAUTH2 SASL-IR UIDPLUS ID UNSELECT CHILDREN IDLE NAMESPACE LITERAL+
C01 OK CAPABILITY completed.
A01 AUTHENTICATE XOAUTH2 dXNlcj1obW9kaUBjb...
A01 NO AUTHENTICATE failed.
* BYE Connection is closed. 13
read:errno=0

Run Code Online (Sandbox Code Playgroud)

这给出了令牌(请注意,范围值与实际工作的令牌不同,权限没有 Outlook url):

public static final List<String> SCOPES = Arrays.asList(
    "offline_access",
    "https%3A%2F%2Foutlook.office365.com%2FIMAP.AccessAsUser.All",
    "https%3A%2F%2Foutlook.office365.com%2FSMTP.Send"
);
Run Code Online (Sandbox Code Playgroud)

这导致了我之前得到的结果:

A1 NO AUTHENTICATE failed.
Run Code Online (Sandbox Code Playgroud)

尝试将所有范围作为 URL:

{
    "token_type": "Bearer",
    "scope": "https://outlook.office365.com/IMAP.AccessAsUser.All https://outlook.office365.com/SMTP.Send",
    "expires_in": 3599,
    "ext_expires_in": 3599,
    "access_token": "eyJ0eXAiOiJKV1....",
    "refresh_token": "OAQABAAAAAAAm...."
}
Run Code Online (Sandbox Code Playgroud)

获取token时出现如下错误(同意步骤成功):

public static final List<String> SCOPES = Arrays.asList(
        "offline_access",
        "User.Read",
        "Mail.ReadWrite",
        "https%3A%2F%2Foutlook.office365.com%2FIMAP.AccessAsUser.All",
        "https%3A%2F%2Foutlook.office365.com%2FSMTP.Send"
);
Run Code Online (Sandbox Code Playgroud)

并在尝试所有范围以获取 microsoft graph 时(从 Azure 门户复制)

{
    "token_type": "Bearer",
    "scope": "IMAP.AccessAsUser.All Mail.ReadWrite SMTP.Send User.Read profile openid email",
    "expires_in": 3599,
    "ext_expires_in": 3599,
    "access_token": "eyJ0eXAiOiJKV1Q...",
    "refresh_token": "OAQABAAAAAAAm..."
}
Run Code Online (Sandbox Code Playgroud)

返回以下令牌(没有刷新令牌,但已请求 offline_access)

A1 NO AUTHENTICATE failed.
Run Code Online (Sandbox Code Playgroud)

没有成功:

A1 NO AUTHENTICATE failed.
Run Code Online (Sandbox Code Playgroud)

所以看起来,如果你没有为范围指定 Outlook url,它可能被假定为不允许通过 IMAP 和 SMTP 授权的图一。

更新 4:

通过在同意步骤中请求我需要的所有范围,然后获取第一个仅具有 Graph 范围的访问令牌,第二个使用指定 Outlook 范围的刷新令牌端点 - 它起作用了。使用刷新令牌方法获取第二个访问令牌是因为如果您尝试通过身份验证代码获取访问令牌,您将收到以下错误:

public static final List<String> SCOPES = Arrays.asList(
        "offline_access", // or "https%3A%2F%2Fgraph.microsoft.com%2Foffline_access"
        "https%3A%2F%2Fgraph.microsoft.com%2FUser.Read",
        "https%3A%2F%2Fgraph.microsoft.com%2FMail.ReadWrite",
        "https%3A%2F%2Foutlook.office365.com%2FIMAP.AccessAsUser.All",
        "https%3A%2F%2Foutlook.office365.com%2FSMTP.Send"
);
Run Code Online (Sandbox Code Playgroud)

所以不,我需要根据我需要管理的资源使用两个单独的令牌。

更新 5:

如果它仍然不起作用 - 检查您的组织是否启用了安全默认值 - 他们禁用帐户的 POP/IMAP/SMTP 身份验证 - https://techcommunity.microsoft.com/t5/exchange-team-blog/annoucing-oauth- 2-0-support-for-imap-and-smtp-auth-protocols-in/bc-p/1544725/highlight/true#M28589

小智 6

IMAP、SMTP 范围针对 Exchange 资源而不是 Graph。而 User.Read、Mail.ReadWrite 用于 Graph 资源。

我们不支持生成用于两种资源的令牌。因此出现错误“为输入参数范围提供的值无效,因为它包含多个资源。”

您应该通过两次调用 /token 分别生成两个令牌。1. 一种为 Exchange 资源生成的 IMAP、SMTP 范围。2. 另一个具有 Graph 范围(User.Read、Mail.ReadWrite)的用于 Graph 资源。

  • 谢谢,这成功了!我使用授权代码获得了图形范围的第一个令牌,在用户同意步骤后收到,并使用刷新令牌获得了 Outlook 范围的第二个令牌,通过第一个令牌获得(这是我在访问两个资源时发现的解决方法) MS Graph API 的范围不同)。我现在的问题是 - 我可以使用通过第一个访问令牌获得的刷新令牌来刷新 Graph 和 Outlook 范围的访问令牌,还是需要使用它们自己的刷新令牌刷新它们? (2认同)
  • 我用 C# 编写了客户端代码的简短工作示例 https://gist.github.com/vSzemkel/e1675b97af5577d76ee260bdfb8a415e (2认同)