【Java编程系列】使用java接入Microsoft Graph,实现发送邮件功能

news2024/12/22 20:53:43

1、前言

        微软与2022年10月1号,开始停止了部分服务的 basic auth (账密登录)功能,需要改用 oauth2.0 协议接入相应服务。邮件方面主要在于IMAP和pop协议。并且与2023年1月1日时,正式全面停止账密登录使用去接入上述服务功能。而对于smtp方面,目前还没有指出,还是可以继续使用账密登录的,而具体的不可用截止日期,目前并未明确指出。但官方还是建议,将smtp也尽早接入使用oauth2.0。

官网最新更新信息:


        基于以上原因,未雨绸缪的想法,担心不久的将来还是要改造,所以不如现在就先了解和实现如何接入oauth2.0去发送邮件吧。
        因此,今天我将记录下,我通过微软的Microsoft Graph API来实现邮件发送的服务功能,该api也是使用到了oauth2.0的接入,所以直接实现即可。

参考文档:

Basic Authentication and Exchange Online – September 2021 Update - Microsoft Community Hub

Basic Authentication Deprecation in Exchange Online – September 2022 Update - Microsoft Community Hub

弃用 Exchange Online 中的基本身份验证 | Microsoft Learn


2、Microsoft Graph接入实践

2.1 参考API

参考链接:

user: sendMail - Microsoft Graph v1.0 | Microsoft Learn

根据文档,会带你跳转进到依赖包的引入页面和身份认证示例代码的界面,如图:

以上,就是官方文档推荐的封装SDK API调用方式的流程。。。。

但是但是,,这一套操作下来,程序根本运行不起来,各种依赖包缺失或是ClassNotFound。

(试过多遍之后,实在无力吐槽,百度其他帖子,也有遇到这种情况)

所以,下面我所介绍的方式,是另外一种形式,通过Http的方式请求Graph Api!!!

另外一些前置准备工作,请按官方推荐步骤来:


2.2 引入依赖

<!-- 发邮件 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

<dependency>
    <groupId>com.microsoft.azure</groupId>
    <artifactId>msal4j</artifactId>
    <version>1.10.1</version>
</dependency>

2.3 实现代码

package xxx.xxx.mail.helper;

import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.microsoft.aad.msal4j.ClientCredentialFactory;
import com.microsoft.aad.msal4j.ClientCredentialParameters;
import com.microsoft.aad.msal4j.ConfidentialClientApplication;
import com.microsoft.aad.msal4j.IAuthenticationResult;
import xxx.xxx.constants.DateConstant;
import xxx.xxx.constants.PunctuationMarkConstant;
import xxx.xxx.util.DateUtils;
import xxx.xxx.util.StringUtils;
import xxx.xxx.mail.bean.po.MicrosoftMailConfig;
import xxx.xxx.mail.dao.MicrosoftMailConfigMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.mail.internet.MimeMessage;
import java.io.ByteArrayOutputStream;
import java.util.Base64;
import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;


@Slf4j
@Component
public class Oauth2GraphHelper {

    private ConcurrentHashMap<String,IAuthenticationResult> tokenMap = new ConcurrentHashMap<>();

    @Autowired
    private MicrosoftMailConfigMapper microsoftMailConfigMapper;

    @Value("${graph.authUrl:https://login.microsoftonline.com/%s/oauth2/v2.0/token}")
    private String authUrl;

    @Value("${graph.sendUrl:https://graph.microsoft.com/v1.0/users/%s/sendMail}")
    private String sendRequestUrl;

    //获取oauth2所需token
    private String getToken(String from) throws Exception {

        //根据发件邮箱查询对应的配置
        MicrosoftMailConfig mailConfig = microsoftMailMapper.selectConfigBySendMail(from);
        if(Objects.isNull(mailConfig)){
            return null;
        }

        //通过配置获取对应邮箱的token
        synchronized (this) {
            String tenantId = mailConfig.getTenantId();
            String key = String.join(PunctuationMarkConstant.PUNCTUATION_VERTICAL_LINE,tenantId,mailConfig.getClientId());
            IAuthenticationResult authenticationResult = tokenMap.get(key);

            if (authenticationResult != null && StringUtils.isNotEmpty(authenticationResult.accessToken())
                    && !isExpires(authenticationResult)) {
                return authenticationResult.accessToken();
            }

            String clientAuthUrl = String.format(authUrl,tenantId);
            ConfidentialClientApplication app = ConfidentialClientApplication.builder(mailConfig.getClientId(), ClientCredentialFactory.createFromSecret(mailConfig.getClientSecret()))
                    .authority(clientAuthUrl).build();
            ClientCredentialParameters clientCredentialParam = ClientCredentialParameters.builder(Collections.singleton("https://graph.microsoft.com/.default")).build();
            CompletableFuture<IAuthenticationResult> future = app.acquireToken(clientCredentialParam);
            IAuthenticationResult iAuthenticationResult = future.get();

            if(Objects.nonNull(iAuthenticationResult)){
                tokenMap.put(key,iAuthenticationResult);
                log.info("MircoSoft Graph Token expiresOnDate {}  {} ",tenantId, DateUtils.formatDate(iAuthenticationResult.expiresOnDate(), DateConstant.DATE_FORMAT_YMD_HMS));
                return iAuthenticationResult.accessToken();
            }

        }

        return null;
    }


    //通过Graph api发送邮件
    public void sendMailByGraphApi(String from,MimeMessage mimeMessage){

        boolean succ = true;
        try {
            String token = getToken(from);
            if(StringUtils.isEmpty(token)){
                log.warn("Graph api obtain token failed !!! By {}",from);
                return;
            }

            String msgBase64 = convertMessageToBase64Str(mimeMessage);
            String reqUrl = String.format(sendRequestUrl,from);
            HttpResponse response = HttpUtil.createPost(reqUrl)
                    .header("Content-Type","text/plain")
                    .header("Authorization","Bearer "+token)
                    .body(msgBase64)
                    .execute();

            if(response.isOk()){
                log.info("Graph api send email by {} , status : {}",from,response.getStatus());
            }else{
                succ = false;
            }

        } catch (Exception e) {
            log.error("",e);
            succ = false;
        }finally {
            if(!succ){
                //可以发送告警信息
                RobotUtil.sendWarnMessage("Graph API failed to send email !!! By {} ",from);
            }
        }

    }

    private boolean isExpires(IAuthenticationResult authenticationResult) {
        long currentTimeMillis = System.currentTimeMillis();
        long time = authenticationResult.expiresOnDate().getTime();
        return time < currentTimeMillis + 1000 * 60 * 10;
    }

    private String convertMessageToBase64Str(MimeMessage mimeMessage) throws Exception {
        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        mimeMessage.writeTo(outputStream);
        final byte[] bytes = outputStream.toByteArray();

        String msgBase64 = Base64.getEncoder().encodeToString(bytes);
        return msgBase64;
    }

}

        简单说明下,authUrl中的占位符需要替换成上述自己申请的 tenantId ,而sendRequestUrl中的占位符需要替换成 发件邮箱  ,如此就可以通过graph api发送邮件啦。。


3、总结

        目前整体流程上,个人觉得比较坑的就是官方的SDK封装的API,我是没用使用成功的。感兴趣的伙伴,可以自行尝试看看,有问题欢迎来交流,就分享到这里啦~~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2183565.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Python报错已解决】[notice] A new release of pip available: 22.2 -> 22.2.2

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 专栏介绍 在软件开发和日常使用中&#xff0c;BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

共享单车轨迹数据分析:以厦门市共享单车数据为例(九)

副标题&#xff1a;基于站点800m范围内评价指标探究——以吕厝站为例 上篇文章我们以厦门市为例&#xff0c;来通过POI和优劣解距离法&#xff08;TOPSIS&#xff09;来研究厦门岛内以800m作为辐射范围的地铁站哪些地铁站发展的最好&#xff0c;根据综合得分指数可以知道&…

架构设计笔记-5-软件工程基础知识-2

知识要点 构件组装是将库中的构件经适当修改后相互连接,或者将它们与当前开发项目中的软件元素连接,最终构成新的目标软件。 构件组装技术大体可分为: 1. 基于功能的组装技术:基于功能的组装技术采用子程序调用和参数传递的方式将构件组装起来。它要求库中的构件以子程序…

KPaaS集成平台与传统集成有什么不同?

企业的业务系统越来越复杂&#xff0c;集成需求也日益增长&#xff0c;同时也面临着如何高效、低成本地实现多系统集成的挑战。传统集成方式虽然能够提供高度定制化的解决方案&#xff0c;但其高昂的成本、复杂的实施过程以及后续的维护难题却让许多企业望而却步。在这样的背景…

【Python报错已解决】KeyError: ‘key‘

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 专栏介绍 在软件开发和日常使用中&#xff0c;BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

Nginx基础详解4(location模块、nginx跨域问题的解决、nginx防盗链的设计原理及应用、nginx模块化解剖)

续Nginx基础详解3&#xff08;nginx.conf核心代码讲解、常用命令解析、Nginx日志切割&#xff09;-CSDN博客 目录 10.location模块详解 10.1默认匹配规则‘ /’ 10.2精确匹配规则‘/’ 10.3正则表达式‘~* \’ 10.4精确正则匹配~ \ 10.5路径匹配‘^~ /’ 11.nginx中解决…

深度解析:Python蓝桥杯青少组精英赛道与高端题型概览

目录 一、蓝桥杯青少组简介二、赛项组别与年龄范围三、比赛内容与题型1. 基础知识范围2. 题型设置2.1 选择题2.2 编程题 3. 考试时长 四、奖项设置与激励措施五、总结 一、蓝桥杯青少组简介 蓝桥杯全国软件和信息技术专业人才大赛&#xff08;简称“蓝桥杯”&#xff09;是由工…

“等保测评实践:提升企业网络安全管理水平“

随着信息技术的快速发展&#xff0c;网络安全问题日益成为企业关注的焦点。等级保护&#xff08;简称等保&#xff09;测评作为提升企业网络安全管理水平的重要手段&#xff0c;其在数字化转型的今天显得尤为重要。本文将探讨如何通过等保测评实践&#xff0c;构建企业网络安全…

PWM 模式

一、介绍 PWM&#xff08;脉宽调制&#xff0c;Pulse-width modulation&#xff09;是一种通过调节脉冲信号的宽度来控制电能输出的方法。PWM是一种方波信号&#xff0c;通常在电子和电气工程中用于调节功率输送&#xff0c;控制电机速度&#xff0c;调节LED亮度&#xff0c;以…

数据结构-3.7.双端队列

一.双端队列的三种形式&#xff1a; 双端队列也可以是只在一端删除和添加&#xff0c;此时就是栈&#xff1b; 双端队列在一端添加&#xff0c;另一端输出&#xff0c;此时就是队列&#xff1b; 二.判断输出序列合法性&#xff1a; 题目&#xff1a;若数据元素输入序列为1&am…

AI产品经理PRD文档与传统产品经理PRD有什么不同呢?

目录 模型输出&#xff1a;说白了&#xff0c;就是你的AI要干啥数据接入&#xff1a;你的AI要吃啥“粮食”验收标准&#xff1a;怎么判断你的AI干得好不好经验总结 你好&#xff0c;我是三桥君 在工作中&#xff0c;当我作为传统产品经理时&#xff0c;通常只需提供产品需求文…

数据结构-3.10.队列的应用

一.树的层次遍历&#xff1a; 新建队列&#xff0c;之后&#xff1a; 首先根节点1入队列&#xff0c;之后他的两个子节点2&#xff0c;3入队列&#xff0c;最后1就可以出队列了&#xff1a; 遍历2&#xff0c;他的两个子节点4&#xff0c;5入队列&#xff0c;最后2就可以出队列…

《论文阅读》PECER:通过动态人格提取和情境情绪推理产生同理心反应 ICASSP 2024

《论文阅读》PECER:通过动态人格提取和情境情绪推理产生同理心反应 ICASSP 2024 前言简介任务定义模型架构Cognitive-Affective Personality PerceiverMulti-source EncoderInteractive Decoder损失函数实验结果可持续发展观点前言 亲身阅读感受分享,细节画图解释,再也不用…

Linux 再入门整理:详解 /etc/fstab 文件

目录 1. 什么是 /etc/fstab2. /etc/fstab 文件的格式2.1 设备文件 (Device)2.2 挂载点 (Mount Point)2.3 文件系统类型 (File System Type)2.4 挂载选项 (Mount Options)2.5 Backup Operation&#xff08;dump 参数&#xff09;2.6 Pass Order (fsck 参数)2.6.1 参数设置2.6.2 …

Linux 进程的基本概念及描述

目录 0.前言 1. 什么是进程 1.1 进程的定义与特性 1.2 进程与线程的区别 2.描述进程 2.1 PCB (进程控制块) 2.2 task_struct 3.查看进程 3.1 查看进程信息 3.1.1 /proc 文件系统 3.1.2 ps 命令 3.1.2 top 和 htop 命令 3.2 获取进程标识符 3.2.1使用命令获取PID 3.2.2 使用C语言…

开发环境搭建之VScode的安装及使用

VScode的下载及安装 Visual Stuio Code&#xff08;以下简称VSCode&#xff09;是微软出的一款免费开源的轻量级编辑器。VSCode支持多平台&#xff0c;有 Windows、Linux和macOS 三个版本&#xff0c;是一个跨平台的编辑器。可通过以下载链接获取&#xff1a;https://code.visu…

基于SpringBoot+Vue的毕业设计选题管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

从零开始搭建UVM平台(六)-加入env

书接上回&#xff1a; 从零开始搭建UVM平台&#xff08;一&#xff09;-只有uvm_driver的验证平台 从零开始搭建UVM平台&#xff08;二&#xff09;-加入factory机制 从零开始搭建UVM平台&#xff08;三&#xff09;-加入objection机制 从零开始搭建UVM平台&#xff08;四&…

【Golang】关于Go语言中的包

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

Oracle架构之数据库备份和RAC介绍

文章目录 1 数据库备份1.1 数据库备份分类1.1.1 逻辑备份与物理备份1.1.2 完全备份/差异备份/增量备份 1.2 Oracle 逻辑备份1.2.1 EXP/IMP1.2.1.1 EXP导出1.2.1.2 EXP关键字说明1.2.1.3 导入1.2.1.4 IMP关键字说明 1.2.2 EXPDP/IMPDP1.2.2.1 数据泵介绍1.2.2.2 数据泵的使用 1.…