Office365的IMAP突然返回“身份验证失败”。

4

我有一段Java代码(如下所示),已经有一段时间没有更改过,并且两周前曾经工作过。现在当我运行它时,突然出现了“AUTHENTICATE FAILED”的错误。我在两台不同的PC上都遇到了这个错误,并验证了使用的凭据在我使用浏览器登录我的Office365邮箱时仍然有效。

Office365这边有什么变化我应该知道吗?

我得到的错误信息是:

javax.mail.AuthenticationFailedException: AUTHENTICATE failed.
    at com.sun.mail.imap.IMAPStore.protocolConnect(IMAPStore.java:732)
    at javax.mail.Service.connect(Service.java:366)
    at javax.mail.Service.connect(Service.java:246)
    at my.application.input.imap.ImapMailBoxReader.processOnMessages(ImapMailBoxReader.java:69)

深入挖掘后,原因似乎是一个 A3 NO AUTHENTICATE failed. 的响应(javax.mail.IMAPStore的第730行)。

我使用的代码如下(使用的是javax.mail版本1.6.2):

package my.application.input.imap;

import my.application.dao.PhysicalTransactionDao;
import com.sun.mail.util.MailSSLSocketFactory;

import javax.mail.*;
import javax.mail.search.AndTerm;
import javax.mail.search.ComparisonTerm;
import javax.mail.search.ReceivedDateTerm;
import javax.mail.search.SearchTerm;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
import java.io.*;
import java.util.function.Consumer;

public class ImapMailBoxReader {

    private String host;
    private String username;
    private String password;

    public static void main(String[] args) {
        ImapMailBoxReader imapReader =  new ImapMailBoxReader(
                "outlook.office365.com",
                "myemail",
                "mypassword");
        LocalDate startDate = LocalDate.of(2022,4,1);
        LocalDate endDate = LocalDate.of(2022,7,1);
        imapReader.processOnMessages("Inbox", startDate, endDate, SomeClass::processMessage);
    }

    public ImapMailBoxReader(String host, String username, String password) {
        this.host = host;
        this.username = username;
        this.password = password;
    }

    /**
     * Returns all messages on or after the given since date, until today. If the given since date is null, all messages
     * are returned
     * @param folder the folder to search through
     * @param since the given since date
     * @param mailConsumer the consumer that will process the messages retrieved
     */
    public void processOnMessages(String folder, LocalDate since, Consumer<Message> mailConsumer) {
        processOnMessages(folder, since, null, mailConsumer);
    }

    /**
     * Runs a given mailconsumer on all messages in the given imap folder that have been received on, or after, the given
     * since date and before the given until date. If since is null, all messages are returned up to the until date.
     * If until is null, all messages are returned from the since date until now. If both are null, all messages are
     * returned.
     * @param folder the folder to search through
     * @param since if specified, only messages from this date on are returned
     * @param mailconsumer the consumer that will be executed on the messages
     */
    public void processOnMessages(String folder, LocalDate since, LocalDate until, Consumer<Message> mailconsumer) {
        try {
            Properties prop = new Properties();
            MailSSLSocketFactory sf = new MailSSLSocketFactory();
            sf.setTrustAllHosts(true);
            prop.setProperty("mail.imap.starttls.enable", "true");
            prop.put("mail.imap.starttls.enable", "true");
            prop.put("mail.imap.ssl.socketFactory", sf);

            //Connect to the server
            Session session = Session.getDefaultInstance(prop, null);
            Store store = session.getStore("imap");
            store.connect(host, username, password);

            //open the inbox folder
            Folder inbox = store.getFolder(folder);
            inbox.open(Folder.READ_ONLY);

            Message[] messages;
            if (since != null) {
                Date startDate = Date.from(since.atStartOfDay(ZoneId.systemDefault()).toInstant());
                SearchTerm newerThan = new ReceivedDateTerm(ComparisonTerm.GE, startDate);
                if (until != null) {
                    Date endDate = Date.from(until.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
                    SearchTerm olderThan = new ReceivedDateTerm(ComparisonTerm.LT, endDate);
                    SearchTerm both = new AndTerm(olderThan, newerThan);
                    messages = inbox.search(both);
                } else {
                    messages = inbox.search(newerThan);
                }
            } else if (until != null) {
                Date endDate = Date.from(until.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
                SearchTerm olderThan = new ReceivedDateTerm(ComparisonTerm.LT, endDate);
                messages = inbox.search(olderThan);
            } else {
                messages = inbox.getMessages();
            }
            for (Message m: messages) {
                mailconsumer.accept(m);
            }
            inbox.close(false);
            store.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Will search through all attachments of the message, and will pass those with the given extension (if provided)
     * to the consumer. Note that the connection to the imap should be open for all this magic to work. this method
     * is intended to be called from a messageconsumer during the processOnMessages method from this class.
     * @param message the message for which the attachments are needed.
     * @param extension if provided, only attachments with this extension will be provided
     * @param attachmentConsumer the consumer that will process the attachments
     * @throws IOException if for some reason the attachments can't be accessed
     * @throws MessagingException for other messaging errors
     */
    public static void processOnAttachments(Message message, String extension, Consumer<InputStream> attachmentConsumer)
            throws IOException, MessagingException {
        Multipart multipart = (Multipart) message.getContent();
        for (int i = 0; i < multipart.getCount(); i++) {
            BodyPart bodyPart = multipart.getBodyPart(i);
            if (bodyPart.getFileName() != null && bodyPart.getFileName().endsWith(extension)) {
                attachmentConsumer.accept(bodyPart.getInputStream());
            }
        }
    }
}

这段代码两周前还可以正常运行,但我并没有进行任何修改且凭证仍然有效...

欢迎提出所有建议。


是的,微软正在禁用基于密码的身份验证,而且可能已经在您使用的服务器上禁用了。您只需在不久的将来切换到基于OAUTH2的身份验证即可。 - Max
那肯定是一个解释,感谢您指引我正确的方向。我正在尝试将实现更改为OAuth2,但到目前为止一直未能验证这是否确实是原因(这不是我的专业领域)。我在这个问题上有一个后续:https://dev59.com/hcTsa4cB1Zd3GeqPI9pB - Yves V.
1个回答

1
您必须使用OAuth2,旧的安全验证方式可能已经过时。例如,可以在Thunderbird中使用。只需查看如何重新激活旧的验证或者使用OAuth2与您的Java客户端配合使用。
请参考https://learn.microsoft.com/fr-fr/exchange/troubleshoot/administration/cannot-connect-mailbox-pop-imap-outlook以重新激活旧的验证。
注:要使用共享邮箱,您必须将邮箱名称作为用户名,并在认证部分使用OAuth2用户+密码和MFA(如果需要),而不是旧的方式(user@domain\sharedmailbox + password)。

目前你的回答不够清晰,请编辑并添加更多细节,以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何撰写好答案的更多信息。 - Pashyant Srivastava
@DLR 我一直在尝试做这件事,但迄今为止都没有成功。如果您有任何代码片段可以将oauth身份验证令牌传递给JavaMail并且可以正常工作,那就太好了。请参见https://dev59.com/hcTsa4cB1Zd3GeqPI9pB - Yves V.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接