In order to improve security, Office365
has upgraded the mail client authentication mode, which needs to use OAuth 2.0
authentication, and discards the old username and password method. Therefore, the mail sending and receiving functions previously developed with JavaMail
will not be available, such as migrating from other mail services to Office365
, the relevant code must be modified in a new way.
Upgrade Instructions
Microsoft official explanation address: https://learn.microsoft.com/zh-cn/exchange/troubleshoot/administration/cannot-connect-mailbox-pop-imap-outlook?source=recommendations
It is recommended that tenants disable basic authentication and migrate to modern authentication tenants for modern clients. That is [modern authentication ( OAuth 2.0
token-based authentication)]
OAuth 2.0 authentication
There are four modes of OAuth 2.0 authentication mode
-
Client authentication mode (POST)
- https://xxxxxxxx/oauth/token?grant_type=client_credentials&client_id=xxxx&client_secret=xxxx
- Third-party client authorization, generally a trusted client, does not require user authorization
-
Resource Password Mode (POST)
- https://xxxxxxxx/oauth/token?grant_type=password&client_id=xxxx&client_secret=xxxx&username=username&password=password
- Authorize directly with the user’s username and password, generally used by internal subsystems
-
Implicit Grant Mode (GET)
- http://xxxxxxxx/oauth/authorize?response_type=token&client_id=xxxx
-
redirect
brings back the access_token after authorization, which is less secure than the authorization code mode
-
Authorization code mode (requires two steps, common and high security)
- The first step is to get the code (GET)
https://xxxxxxxx/oauth/authorize?response_type=code&client_id=xxxx
- The second step is to get the token, you need to pass the code (POST) returned in the previous step
https://xxxxxxxx/oauth/token?grant_type=authorization_code&code=code&client_id=xxxx
- The first step is to get the code (GET)
Learn more about OAuth2
: https://learn.microsoft.com/zh-cn/azure/active-directory/develop/active-directory-v2-protocols
Prepare in advance
At the moment it seems that basic authentication in SMTP mode is preserved:
When Basic authentication is permanently disabled on October 1, 2022, SMTP authentication will still be available. The reason SMTP is still available is that many multifunction devices, such as printers and scanners, cannot be updated to use modern authentication. However, we strongly recommend that customers not use Basic Authentication and SMTP AUTH if possible. Other options for sending authenticated mail include using alternative protocols such as the Microsoft Graph API .
First you should register the APP: https://learn.microsoft.com/zh-cn/azure/active-directory/develop/quickstart-register-app
Since the server sends and receives emails in the background, it is the default email address and account, so client_credentials
mode is selected, the corresponding APP
is configured on Office365
, and tenant
, clientId
and clientSecret
are obtained.
Introduction to authentication: https://learn.microsoft.com/zh-cn/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth
There are three main configuration items that need to be used:
Application (Client) ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx
Directory (tenant) ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx
clientSecret (12 months): xxxx~xxxxxxxxxxxxxxxxxxxxxxxxxx
service address
Configuration service address: https://support.microsoft.com/zh-cn/office/pop-imap-%E5%92%8C-smtp-%E8%AE%BE%E7%BD%AE-8361e398-8af4- 4e97-b147-6c6c4ac95353
Email provider | IMAP settings | POP settings | SMTP settings |
---|---|---|---|
Microsoft 365 Outlook Hotmail Live.com |
Server: outlook.office365.com Port: 993 Encryption: SSL/TLS |
Server: outlook.office365.com Port: 995 Encryption: SSL/TLS |
Server: smtp.office365.com Port: 587 Encryption: STARTTLS |
Java client upgrade
The JavaMail client needs to be upgraded:
Document address: https://javaee.github.io/javamail/OAuth2
After upgrading to 1.5.5
, the latest should be 1.6.2
.
does not support pop3
Transferring OAuth2
tokens from JavaMail
does not support POP3
, only IMAP
protocol can be used
Executing with the pop3
protocol will report an error:
javax.mail.AuthenticationFailedException: Protocol error. Connection is closed. 10
at com.sun.mail.pop3.POP3Store.protocolConnect(POP3Store.java:213)
at javax.mail.Service.connect(Service.java:366)
at javax.mail.Service.connect(Service.java:246)
actual code
Simple dependencies, httpclient
, slf4j
log, jackson
‘s json
parser, and javax.mail
<dependencies> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.14</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.14.2</version> </dependency> <dependency> <groupId>com.sun.mail</groupId> <artifactId>javax.mail</artifactId> <version>1.6.2</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.36</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.11</version> </dependency> </dependencies>
Test code:
public class Office365MailTest { private static final Logger LOGGER = LoggerFactory.getLogger(Office365MailTest.class); private static final String SMTP_SERVER_ADDR = "smtp.office365.com"; private static final int SMTP_SERVER_PORT = 587; private static final String IMAP_SERVER_ADDR = "outlook.office365.com"; private static final int IMAP_SERVER_PORT = 993; private static final String USER_NAME = "[email protected]"; private static final String MAIL_FROM = "[email protected]"; private static final String MAIL_TO = "[email protected]"; private static final String MAIL_SUBJECT = "Test测试邮件标题"; private static final String MAIL_CONTENT = "Test测试邮件正文"; private static final String TENANT_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"; private static final String CLIENT_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"; private static final String CLIENT_SECRET = "xxxxxxxxxxxxxxxxxxxxxxxxx"; public static void main(String[] args) throws Exception { // sendEmail(false); recvMail(false); } private static void sendEmail(boolean debug) throws Exception { String protocol = "smtp"; Properties props = getBaseProperties(protocol, SMTP_SERVER_ADDR, SMTP_SERVER_PORT, debug); final Session session = Session.getInstance(props); try { final Message message = new MimeMessage(session); message.setRecipient(Message.RecipientType.TO, new InternetAddress(MAIL_TO)); message.setFrom(new InternetAddress(MAIL_FROM)); message.setSubject(MAIL_SUBJECT); message.setText(MAIL_CONTENT); message.setSentDate(new Date()); Transport transport = session.getTransport(protocol); transport.connect(SMTP_SERVER_ADDR, USER_NAME, getAuthToken(TENANT_ID, CLIENT_ID, CLIENT_SECRET)); transport.send(message); } catch (final MessagingException ex) { LOGGER.error("邮件发送错误", ex); } } private static void recvMail(boolean debug) throws Exception { String protocol = "imap"; Properties props = getBaseProperties(protocol, IMAP_SERVER_ADDR, IMAP_SERVER_PORT, debug); props.put("mail.store.protocol", protocol); String token = getAuthToken(TENANT_ID, CLIENT_ID, CLIENT_SECRET); Session session = Session.getInstance(props); Store store = session.getStore(protocol); store.connect(IMAP_SERVER_ADDR, USER_NAME, token); Folder folder = store.getFolder("INBOX"); // 读inbox folder.open(Folder.READ_WRITE); Message[] messages = folder.getMessages(); for (Message message : messages) { LOGGER.info("{}", message.getSubject()); } } private static Properties getBaseProperties(String protocol, String address, Integer port, boolean debug) { Properties props = new Properties(); props.put("mail." + protocol + ".host", address); props.put("mail." + protocol + ".port", port); if ("smtp".equals(protocol)) { props.put("mail." + protocol + ".starttls.enable", "true"); } else { props.put("mail." + protocol + ".ssl.enable", "true"); } props.put("mail." + protocol + ".ssl.protocols", "TLSv1.2"); props.put("mail." + protocol + ".auth", "true"); props.put("mail." + protocol + ".auth.mechanisms", "XOAUTH2"); if (debug) { // 调试模式props.put("mail.debug", "true"); props.put("mail.debug.auth", "true"); } return props; } private static String getAuthToken(String tenant, String clientId, String clientSecret) throws Exception { CloseableHttpClient client = null; CloseableHttpResponse loginResponse = null; try { SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, (cert, authType) -> true).build(); SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); client = HttpClients.custom().setSSLSocketFactory(sslSocketFactory).build(); HttpPost loginPost = new HttpPost("https://login.microsoftonline.com/" + tenant + "/oauth2/v2.0/token"); String scopes = "https://outlook.office365.com/.default"; String encodedBody = "client_id=" + clientId + "&scope=" + scopes + "&client_secret=" + clientSecret + "&grant_type=client_credentials"; loginPost.setEntity(new StringEntity(encodedBody, ContentType.APPLICATION_FORM_URLENCODED)); loginPost.addHeader(new BasicHeader("cache-control", "no-cache")); loginResponse = client.execute(loginPost); InputStream inputStream = loginResponse.getEntity().getContent(); Map<String, String> parsed = new ObjectMapper().readValue(inputStream, Map.class); return parsed.get("access_token"); } finally { HttpClientUtils.closeQuietly(client); HttpClientUtils.closeQuietly(loginResponse); } } }
Email received successfully:
16:26:41.567 [main] INFO com.fugary.mail.Office365MailTest - test标题16:26:42.189 [main] INFO com.fugary.mail.Office365MailTest - test 16:26:42.811 [main] INFO com.fugary.mail.Office365MailTest - 最新测试版邮件发送
common mistakes
- IMAP error
javax.mail.MessagingException: A3 BAD User is authenticated but not connected.
;
nested exception is:
com.sun.mail.iap.BadCommandException: A3 BAD User is authenticated but not connected.
The problem is generally that the user does not have permission, you can configure the permission or change to a user with permission.
- SMTP error
javax.mail.AuthenticationFailedException: 535 5.7.139 Authentication unsuccessful, SmtpClientAuthentication is disabled for the Tenant. Visit https://aka.ms/smtp_auth_disabled for more information. [SJ0PR03CA0292.namprd03.prod.outlook-08T.com 2023 56:02.982Z 08DB1AFAC2F526FF]
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) at javax.mail.Service.connect(Service.java:246) at com.fugary.mail.Office365MailTest.sendEmail(Office365MailTest.java:66) at com.fugary.mail.Office365MailTest.main(Office365MailTest.java:50)
The SMTP client is not enabled.
This article is transferred from https://fugary.com/?p=434
This site is only for collection, and the copyright belongs to the original author.