diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/mail/GlobalMailAccount.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/mail/GlobalMailAccount.java index f1e392a11..2d14c05ea 100644 --- a/hutool-extra/src/main/java/org/dromara/hutool/extra/mail/GlobalMailAccount.java +++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/mail/GlobalMailAccount.java @@ -14,15 +14,28 @@ package org.dromara.hutool.extra.mail; import org.dromara.hutool.core.io.IORuntimeException; +import java.nio.charset.Charset; + /** * 全局邮件帐户,依赖于邮件配置文件{@link MailAccount#MAIL_SETTING_PATHS} * * @author looly - * */ public enum GlobalMailAccount { + /** + * 单例 + */ INSTANCE; + // mime + private static final String SPLIT_LONG_PARAMS = "mail.mime.splitlongparameters"; + private static final String CHARSET = "mail.mime.charset"; + + static { + System.setProperty(SPLIT_LONG_PARAMS, "false"); + System.setProperty(CHARSET, INSTANCE.mailAccount.getCharset().name()); + } + private final MailAccount mailAccount; /** @@ -41,6 +54,28 @@ public enum GlobalMailAccount { return this.mailAccount; } + /** + * 设置对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名)
+ * 注意此项为全局设置,此项会调用 + *
+	 * System.setProperty("mail.mime.splitlongparameters", true)
+	 * 
+ * + * @param splitLongParams 对于超长参数是否切分为多份 + */ + public void setSplitLongParams(final boolean splitLongParams) { + System.setProperty(SPLIT_LONG_PARAMS, String.valueOf(splitLongParams)); + } + + /** + * 设置全局默认编码 + * + * @param charset 编码 + */ + public void setCharset(final Charset charset) { + System.setProperty(CHARSET, charset.name()); + } + /** * 创建默认帐户 * diff --git a/hutool-extra/src/main/java/org/dromara/hutool/extra/mail/MailAccount.java b/hutool-extra/src/main/java/org/dromara/hutool/extra/mail/MailAccount.java index 2e2d242a9..94be9cc12 100644 --- a/hutool-extra/src/main/java/org/dromara/hutool/extra/mail/MailAccount.java +++ b/hutool-extra/src/main/java/org/dromara/hutool/extra/mail/MailAccount.java @@ -13,6 +13,7 @@ package org.dromara.hutool.extra.mail; import org.dromara.hutool.core.array.ArrayUtil; +import org.dromara.hutool.core.lang.Assert; import org.dromara.hutool.core.util.CharsetUtil; import org.dromara.hutool.core.util.ObjUtil; import org.dromara.hutool.core.text.StrUtil; @@ -36,6 +37,8 @@ public class MailAccount implements Serializable { private static final String SMTP_HOST = "mail.smtp.host"; private static final String SMTP_PORT = "mail.smtp.port"; private static final String SMTP_AUTH = "mail.smtp.auth"; + // 认证机制,多个机制使用空格或逗号隔开,如:XOAUTH2 + private static final String SMTP_AUTH_MECHANISMS = "mail.smtp.auth.mechanisms"; private static final String SMTP_TIMEOUT = "mail.smtp.timeout"; private static final String SMTP_CONNECTION_TIMEOUT = "mail.smtp.connectiontimeout"; private static final String SMTP_WRITE_TIMEOUT = "mail.smtp.writetimeout"; @@ -48,9 +51,6 @@ public class MailAccount implements Serializable { private static final String SOCKET_FACTORY_FALLBACK = "mail.smtp.socketFactory.fallback"; private static final String SOCKET_FACTORY_PORT = "smtp.socketFactory.port"; - // System Properties - private static final String SPLIT_LONG_PARAMS = "mail.mime.splitlongparameters"; - // 其他 private static final String MAIL_DEBUG = "mail.debug"; @@ -71,6 +71,10 @@ public class MailAccount implements Serializable { * 是否需要用户名密码验证 */ private Boolean auth; + /** + * 认证机制,多个机制使用空格或逗号隔开,如:XOAUTH2 + */ + private String authMechanisms; /** * 用户名 */ @@ -94,10 +98,6 @@ public class MailAccount implements Serializable { * 编码用于编码邮件正文和发送人、收件人等中文 */ private Charset charset = CharsetUtil.UTF_8; - /** - * 对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名) - */ - private boolean splitlongparameters = false; /** * 对于文件名是否使用{@link #charset}编码,默认为 {@code true} */ @@ -174,6 +174,14 @@ public class MailAccount implements Serializable { */ public MailAccount(final Setting setting) { setting.toBean(this); + + // custom property + // 对于用户希望直接在配置文件中设置mail.xxx参数的情况,在此加入 + setting.forEach((key, value) -> { + if (StrUtil.startWith(key, "mail.")) { + this.setCustomProperty(key, value); + } + }); } // -------------------------------------------------------------- Constructor end @@ -238,6 +246,26 @@ public class MailAccount implements Serializable { return this; } + /** + * 获取认证机制,多个机制使用空格或逗号隔开,如:XOAUTH2 + * + * @return 认证机制 + */ + public String getAuthMechanisms() { + return this.authMechanisms; + } + + /** + * 设置认证机制,多个机制使用空格或逗号隔开,如:XOAUTH2 + * + * @param authMechanisms 认证机制 + * @return this + */ + public MailAccount setAuthMechanisms(final String authMechanisms) { + this.authMechanisms = authMechanisms; + return this; + } + /** * 获取用户名 * @@ -349,28 +377,6 @@ public class MailAccount implements Serializable { return this; } - /** - * 对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名) - * - * @return 对于超长参数是否切分为多份 - */ - public boolean isSplitlongparameters() { - return splitlongparameters; - } - - /** - * 设置对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名)
- * 注意此项为全局设置,此项会调用 - *
-	 * System.setProperty("mail.mime.splitlongparameters", true)
-	 * 
- * - * @param splitlongparameters 对于超长参数是否切分为多份 - */ - public void setSplitlongparameters(final boolean splitlongparameters) { - this.splitlongparameters = splitlongparameters; - } - /** * 对于文件名是否使用{@link #charset}编码,默认为 {@code true} * @@ -583,14 +589,15 @@ public class MailAccount implements Serializable { * @return {@link Properties} */ public Properties getSmtpProps() { - //全局系统参数 - System.setProperty(SPLIT_LONG_PARAMS, String.valueOf(this.splitlongparameters)); - final Properties p = new Properties(); p.put(MAIL_PROTOCOL, "smtp"); p.put(SMTP_HOST, this.host); p.put(SMTP_PORT, String.valueOf(this.port)); p.put(SMTP_AUTH, String.valueOf(this.auth)); + // issue#3687 增加Oath2认证方式支持 + if(StrUtil.isNotBlank(this.authMechanisms)){ + p.put(SMTP_AUTH_MECHANISMS, this.authMechanisms); + } if (this.timeout > 0) { p.put(SMTP_TIMEOUT, String.valueOf(this.timeout)); } @@ -638,7 +645,7 @@ public class MailAccount implements Serializable { * @return this */ public MailAccount defaultIfEmpty() { - // 去掉发件人的姓名部分 + Assert.notBlank(this.from, "'from' must not blank!"); final String fromAddress = InternalMailUtil.parseFirstAddress(this.from, this.charset).getAddress(); if (StrUtil.isBlank(this.host)) { @@ -669,6 +676,6 @@ public class MailAccount implements Serializable { @Override public String toString() { return "MailAccount [host=" + host + ", port=" + port + ", auth=" + auth + ", user=" + user + ", pass=" + (ArrayUtil.isEmpty(this.pass) ? "" : "******") + ", from=" + from + ", startttlsEnable=" - + starttlsEnable + ", socketFactoryClass=" + socketFactoryClass + ", socketFactoryFallback=" + socketFactoryFallback + ", socketFactoryPort=" + socketFactoryPort + "]"; + + starttlsEnable + ", socketFactoryClass=" + socketFactoryClass + ", socketFactoryFallback=" + socketFactoryFallback + ", socketFactoryPort=" + socketFactoryPort + "]"; } } diff --git a/hutool-extra/src/test/java/org/dromara/hutool/extra/mail/Oauth2Test.java b/hutool-extra/src/test/java/org/dromara/hutool/extra/mail/Oauth2Test.java new file mode 100644 index 000000000..d0009c35e --- /dev/null +++ b/hutool-extra/src/test/java/org/dromara/hutool/extra/mail/Oauth2Test.java @@ -0,0 +1,70 @@ +package org.dromara.hutool.extra.mail; + +import jakarta.mail.*; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; +import org.dromara.hutool.core.lang.Console; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.Properties; + +public class Oauth2Test { + @Test + @Disabled + void sendTest() { + final MailAccount mailAccount = new MailAccount(); + mailAccount.setHost("smtp.office365.com"); + mailAccount.setPort(587); + mailAccount.setFrom("xxxx@outlook.com"); + mailAccount.setUser("xxxx"); + // 这里使用生成的token + mailAccount.setPass("token".toCharArray()); + mailAccount.setAuth(true); + mailAccount.setStarttlsEnable(true); + // 这里关掉SSL + mailAccount.setSslEnable(false); + // 使用XOAUTH2 + mailAccount.setCustomProperty("mail.smtp.auth.mechanisms", "XOAUTH2"); + mailAccount.setCustomProperty("mail.smtp.auth.login.disable", "true"); + mailAccount.setCustomProperty("mail.smtp.auth.plain.disable", "true"); + + final String id = Mail.of(mailAccount) + .setTos("xxx@qq.com") + .setContent("Mail test from Outlook!") + .setTitle("测试Outlook邮件") + .send(); + + Console.log(id); + } + + /** + * https://medium.com/@tempmailwithpassword/sending-emails-with-java-using-oauth2-and-office-365-b164d54f68fc + * + * @throws MessagingException 异常 + */ + @Test + @Disabled + void sendTest2() throws MessagingException { + final Properties props = new Properties(); + props.put("mail.smtp.auth", "true"); + props.put("mail.smtp.starttls.enable", "true"); + props.put("mail.smtp.host", "smtp.office365.com"); + props.put("mail.smtp.port", "587"); + props.put("mail.smtp.auth.mechanisms", "XOAUTH2"); + props.put("mail.smtp.auth.login.disable", "true"); + props.put("mail.smtp.auth.plain.disable", "true"); + final Session session = Session.getInstance(props, new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication("xx", "123"); + } + }); + final MimeMessage message = new MimeMessage(session); + message.setFrom(new InternetAddress("xxx@outlook.com")); + message.addRecipient(Message.RecipientType.TO, new InternetAddress("xxx@qq.com")); + message.setSubject("Your Subject Here"); + message.setText("Email body content here."); + Transport.send(message); + } +}