目录
问题提出
在2022年10月份,微软公司出于安全考虑,陆续取消了Exchange邮件发送的Basic认证。通俗来说就是不能再用用户名密码来直接进行认证了,但是系列的邮件服务不能停,所以需要切换Exchange邮件的认证和发送方式,所以首先放弃掉以前的ews-java-api相关的所有jar包,重新开始。
微软官方目前推荐以Graph的方式来发送Outlook系列邮件。
问解决题
话不多说,直接贴代码,替换必要参数后,可以直接运行:
快速解决方式
public static void main(String[] args) throws IOException {
ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
.clientId("clientId")
.clientSecret("clientSecret")
.tenantId("tenantId")
.build();
final TokenCredentialAuthProvider tokenCredAuthProvider = new TokenCredentialAuthProvider(Arrays.asList("https://graph.microsoft.com/.default"), clientSecretCredential);
System.out.println("First Step Reached. ");
test(tokenCredAuthProvider);
}
public static void test(IAuthenticationProvider authProvider) throws IOException {
GraphServiceClient<Request> graphClient = GraphServiceClient.builder().authenticationProvider(authProvider).buildClient();
Message message = new Message();
message.subject = "Meet for lunch?";
ItemBody body = new ItemBody();
body.contentType = BodyType.TEXT;
body.content = "The new cafeteria is open.";
message.body = body;
LinkedList<Recipient> toRecipientsList = new LinkedList<Recipient>();
Recipient toRecipients = new Recipient();
EmailAddress emailAddress = new EmailAddress();
emailAddress.address = "bigbearxyz@outlook.com";
toRecipients.emailAddress = emailAddress;
toRecipientsList.add(toRecipients);
message.toRecipients = toRecipientsList;
//构建附件
LinkedList<Attachment> attachmentsList = new LinkedList<Attachment>();
FileAttachment attachments = new FileAttachment();
attachments.name = "1111.txt";
attachments.oDataType = "#microsoft.graph.fileAttachment";
attachments.contentType="text/plain";
attachments.contentBytes = Base64.getDecoder().decode("SGVsbG8gV29ybGQh");
attachmentsList.add(attachments);
AttachmentCollectionResponse attachmentCollectionResponse = new AttachmentCollectionResponse();
attachmentCollectionResponse.value = attachmentsList;
AttachmentCollectionPage attachmentCollectionPage = new AttachmentCollectionPage(attachmentCollectionResponse, null);
message.attachments = attachmentCollectionPage;
//以指定用户邮箱发送邮件
graphClient.users("sender@outlook.com")
.sendMail(UserSendMailParameterSet.newBuilder().
withMessage(message).
withSaveToSentItems(null).build())
.buildRequest()
.post();
}
生产解决方式
这里的生产解决方式是对快速解决方式的补充,快速解决方式在实际的生产中使用会存在一些问题,主要是和附件相关。
首先是前面解决方式中是把所有的附件放入一个对象,而graph发送邮件附件是有大小限制的,当超过3M以后,需要用超大邮件专用的发送方式,否则会报错。
而上一个解决方法里面,是可以添加多份附件,但是只要附件的大小总和大于3M,这个邮件就无法发送,所以有了下面的面向生产的解决方案。
生产的解决方案是,把附件逐个上传,这样每个附件的大小就可以限制在3M,这种应该可以满足绝大多数的附件发送情况。
在发送的过程中,如果有大于3M的邮件,那么使用超大附件传输的方法,发送超大邮件,代码经过实际测试,可以直接使用。
public static void main(String[] args) throws IOException {
ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
.clientId("clientId")
.clientSecret("clientSecret")
.tenantId("tenantId")
.build();
final TokenCredentialAuthProvider tokenCredAuthProvider = new TokenCredentialAuthProvider(Arrays.asList("https://graph.microsoft.com/.default"), clientSecretCredential);
System.out.println("First Step Reached. ");
buildDraftMessage(tokenCredAuthProvider);
}
private static final long MB = 1024 * 1024;
private static final String SENDER_MAIL = "sender@outlook.com";
private static final String RECIPIENT_MAIL = "accept@outlook.com";
public static void buildDraftMessage(IAuthenticationProvider authProvider) throws IOException {
GraphServiceClient graphClient = GraphServiceClient.builder().authenticationProvider(authProvider).buildClient();
Message message = new Message();
message.subject = "Did you see last night's game?";
ItemBody body = new ItemBody();
body.contentType = BodyType.HTML;
body.content = "They were <b>awesome</b>!";
message.body = body;
LinkedList<Recipient> toRecipientsList = new LinkedList<Recipient>();
Recipient toRecipients = new Recipient();
EmailAddress emailAddress = new EmailAddress();
emailAddress.address = RECIPIENT_MAIL;
toRecipients.emailAddress = emailAddress;
toRecipientsList.add(toRecipients);
message.toRecipients = toRecipientsList;
//构建草稿
Message post = graphClient.users(SENDER_MAIL).messages()
.buildRequest()
.post(message);
//构建附件
buildAttach(authProvider, post);
//发送草稿邮件
graphClient.users(SENDER_MAIL).messages(post.id)
.send()
.buildRequest()
.post();
}
private static void buildAttach(IAuthenticationProvider authProvider, Message message) throws IOException {
File file = new File("path");
FileInputStream fileInputStream = new FileInputStream(file);
int available = fileInputStream.available();
if (available >= 3 * MB) {
//附件大于3M,使用大附件专用发送方法
bigAttach(authProvider, message, file);
} else {
//附件小于3M,使用普通发送方法
commonAttach(authProvider, message, fileInputStream);
}
}
public static byte[] toByteArray(InputStream input) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[1024 * 4];
int n = 0;
while (-1 != (n = input.read(buffer))) {
output.write(buffer, 0, n);
}
return output.toByteArray();
}
private static void commonAttach(IAuthenticationProvider authProvider, Message message, FileInputStream fileInputStream) throws IOException {
GraphServiceClient graphClient = GraphServiceClient.builder().authenticationProvider(authProvider).buildClient();
FileAttachment attachment = new FileAttachment();
attachment.oDataType = "#microsoft.graph.fileAttachment";
attachment.name = "smile.pdf";
attachment.contentBytes = toByteArray(fileInputStream);
graphClient.users(SENDER_MAIL).messages(message.id).attachments()
.buildRequest()
.post(attachment);
}
public static void bigAttach(IAuthenticationProvider authProvider, Message message, File file) throws IOException {
GraphServiceClient graphClient = GraphServiceClient.builder().authenticationProvider(authProvider).buildClient();
FileInputStream fileInputStream = new FileInputStream(file);
int available = fileInputStream.available();
AttachmentItem attachmentItem = new AttachmentItem();
attachmentItem.attachmentType = AttachmentType.FILE;
attachmentItem.name = "flower.pdf";
attachmentItem.size = (long) available;
UploadSession uploadSession = graphClient.users(SENDER_MAIL).messages(message.id).attachments()
.createUploadSession(AttachmentCreateUploadSessionParameterSet
.newBuilder()
.withAttachmentItem(attachmentItem)
.build())
.buildRequest()
.post();
OkHttpClient client = new OkHttpClient().newBuilder().build();
MediaType mediaType = MediaType.parse("application/octet-stream");
RequestBody body = RequestBody.create(mediaType, file);
Request request = new Request.Builder()
.url(uploadSession.uploadUrl)
.method("PUT", body)
.addHeader("Content-Length", String.valueOf(attachmentItem.size))
.addHeader("Content-Range", "bytes 0-" + (attachmentItem.size - 1) + "/" + attachmentItem.size)
.addHeader("Content-Type", "application/octet-stream")
.build();
Response response = client.newCall(request).execute();
System.out.println(response.body().string());
}
注意
使用如上代码有几点需要注意:
1、注册应用程序
首先你需要登录微软云平台,搜索Azure Active Directory配置一个应用。
具体步骤如下:
放下文档链接:配置应用程序文档。
按照笔者的经验,学会注册就可以了,不要完全按文档来,对于Java代码级发送邮件基本没帮助。
这样就可以获取到上面的一系列参数了。
2、引入需要依赖jar包——重点
<dependency>
<groupId>com.microsoft.graph</groupId>
<artifactId>microsoft-graph-auth</artifactId>
<version>0.3.0</version>
</dependency>
<dependency>
<!-- Include the sdk as a dependency -->
<groupId>com.microsoft.graph</groupId>
<artifactId>microsoft-graph-core</artifactId>
<version>2.0.14</version>
</dependency>
<dependency>
<!-- Include the sdk as a dependency -->
<groupId>com.microsoft.graph</groupId>
<artifactId>microsoft-graph</artifactId>
<version>5.42.0</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.3.3</version>
</dependency>
这个版本配置,是笔者的心血之作,调试了好久才最终成型,这里要好好吐槽一下微软官方文档,他们依赖配置的错误百出,按照文档完全运行不了。
3、微软管理员授予应用合适权限
需要在AD应用的API权限菜单配置合适的Graph权限,最要紧的是要管理员授予权限,之后就可以正常访问了。
4、配置maven仓库
微软没有开放公网仓库,所以需要引入指定的maven仓库,github链接:
<profiles>
<profile>
<id>allow-snapshots</id>
<activation><activeByDefault>true</activeByDefault></activation>
<repositories>
<repository>
<id>snapshots-repo</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases><enabled>false</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
</profile>
</profiles>
实际项目引入模式为:
参考链接
最后,再给大家提供几个微软获取token的参考文档:
最后说一句,直接用我的代码及pom即可。