【发布时间】:2020-08-19 04:14:09
【问题描述】:
最近support for OAuth 2.0 for IMAP and SMTP in the Exchange Online 已公布。
在the guide 之后,我设置了应用程序权限以及 IMAP 和 SMTP 连接。
应用配置为Accounts in any organizational directory (Any Azure AD directory - Multitenant),使用authorization code flow。
以下网址用于授权:
- https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize
- https://login.microsoftonline.com/organizations/oauth2/v2.0/token
并添加了以下委派 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"
);
我成功收到访问和刷新令牌:
{
"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"
}
这是用于连接 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);
生成以下输出:
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)
以下代码用于连接 SMTP:
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);
提供以下输出:
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)
我也尝试过:
- 将作用域指定为
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
更新 2:
尝试在 Azure 门户中创建具有以下权限的全新应用程序:
在尝试同意范围时收到以下屏幕:
这很奇怪,因为来自 Azure 门户的权限没有指定需要管理员同意,而且我之前的应用注册在请求 IMAP 和 SMTP 范围时没有显示这样的屏幕。
更新 3:
感谢 cmets 的这篇文章,我尝试了以下范围:
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"
);
这给了我下面的令牌:
{
"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...."
}
IMAP/SMTP 身份验证成功,我能够阅读收件箱 + 发送电子邮件!
但对于我的应用程序,我还需要几个其他范围来使用一些 MS Graph API 端点(读取用户配置文件、消息订阅和消息删除)。
所以我尝试了不同的范围:
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"
);
这给了令牌(注意范围值与实际工作的令牌不同,权限没有outlook url):
{
"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..."
}
这导致了我之前得到的结果:
A1 NO AUTHENTICATE failed.
尝试将所有范围都作为 URL:
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"
);
获取token时导致如下错误(同意步骤成功通过):
{
"error": "invalid_request",
"error_description": "AADSTS28000: Provided value for the input parameter scope is not valid because it contains more than one resource. Scope offline_access https://graph.microsoft.com/user.read https://graph.microsoft.com/mail.readwrite https://outlook.office365.com/imap.accessasuser.all https://outlook.office365.com/smtp.send is not valid.\r\nTrace ID: c3282396-6231-4e11-8300-77bc2ca57f00\r\nCorrelation ID: 5f5145bf-7114-4e6c-ab11-30e7ff84a056\r\nTimestamp: 2020-05-06 08:08:48Z",
"error_codes": [
28000
],
"timestamp": "2020-05-06 08:08:48Z",
"trace_id": "c3282396-6231-4e11-8300-77bc2ca57f00",
"correlation_id": "5f5145bf-7114-4e6c-ab11-30e7ff84a056"
}
并且在尝试所有范围以拥有 microsoft graph(从 Azure 门户复制)时
public static final List<String> SCOPES = Arrays.asList(
"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%2Fgraph.microsoft.com%2FIMAP.AccessAsUser.All",
"https%3A%2F%2Fgraph.microsoft.com%2FSMTP.Send"
);
返回以下令牌(尽管已请求离线访问但没有刷新令牌)
{
"token_type": "Bearer",
"scope": "profile openid email https://graph.microsoft.com/IMAP.AccessAsUser.All https://graph.microsoft.com/Mail.ReadWrite https://graph.microsoft.com/SMTP.Send https://graph.microsoft.com/User.Read",
"expires_in": 3599,
"ext_expires_in": 3599,
"access_token": "eyJ0eXAiOiJKV1..."
}
没有成功:
A1 NO AUTHENTICATE failed.
因此,如果您没有为范围指定 Outlook url,它可能被假定为不允许通过 IMAP 和 SMTP 进行授权的图一。
更新 4:
通过在同意步骤中请求我需要的所有范围,然后获取仅具有 Graph 范围的第一个访问令牌和使用指定 Outlook 范围的刷新令牌端点的第二个访问令牌 - 它有效。 使用刷新令牌方法获取第二个访问令牌,因为如果您尝试通过验证码获取访问令牌,您将收到以下错误:
{
"error": "invalid_grant",
"error_description": "AADSTS54005: OAuth2 Authorization code was already redeemed, please retry with a new valid code or use an existing refresh token.\r\nTrace ID: 09fc80f4-f5fd-4e52-938f-d56b71dd0900\r\nCorrelation ID: 4f35e05c-23c8-4fdc-a5a7-2fcde5a73b44\r\nTimestamp: 2020-05-08 12:13:30Z",
"error_codes": [
54005
],
"timestamp": "2020-05-08 12:13:30Z",
"trace_id": "09fc80f4-f5fd-4e52-938f-d56b71dd0900",
"correlation_id": "4f35e05c-23c8-4fdc-a5a7-2fcde5a73b44"
}
所以不,我需要使用两个单独的令牌,具体取决于我需要管理的资源。
更新 5:
如果它仍然不起作用 - 检查您的组织是否启用了安全默认值 - 他们禁用帐户的 POP/IMAP/SMTP 身份验证 - https://techcommunity.microsoft.com/t5/exchange-team-blog/announcing-oauth-2-0-support-for-imap-and-smtp-auth-protocols-in/bc-p/1544725/highlight/true#M28589
【问题讨论】:
-
只是一个问题——你知道访问令牌只能使用一次,对吧?每次使用一个,您都会收到一个新的令牌以供下次使用。
-
在大多数 oauth 系统中,访问令牌的有效期为一小时;并且需要定期刷新。 (澄清:OAUTH 系统与我有经验的电子邮件一起使用。)
-
@arnt,那真的很奇怪。我可以将获得的访问令牌用于其他 HTTP 请求,直到它过期。无论如何,无论我之前使用了多少次令牌,IMAP/SMTP OAuth 身份验证都会失败。
-
忘掉我之前的通讯吧,我没有好好思考。我将 oauth 的特征与我编写的代码的特征(每小时重新连接一次,因此每次都需要一个新令牌,因为生命周期短于 1 小时)混淆了。对不起。
-
如果这是刚刚宣布的,微软可能还在迭代它。尝试删除 offline_access 和 user_read,并将 SMTP 和 IMAP 保留为所需的最低权限。
标签: java oauth-2.0 smtp office365 imap