JavaMail 与 Oauth 和 Office365

2024-03-31

我正在用 Java 构建一个简单的命令行应用程序,它可以登录我的电子邮件箱 (IMAP) 并下载所有附件。我使用了基本身份验证,但 Microsoft 正在禁用它,因此我尝试将我的应用程序转换为使用 OAuth。

在阅读了不同的 OAuth 流程后,似乎对于我的简单独立命令行应用程序来说,简单地对密码进行硬编码是没有问题的,资源所有者密码凭证大(如所述here https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc)将是最好的(或好的)选择。我进一步根据以下指示这个来源 https://javaee.github.io/javamail/OAuth2其中描述了如何使用最新版本的 Javamail 启用 OAuth。

将它们放在一起似乎有点困难,而且我不断收到 AUTHENTICATE Failed 错误。

那么,我尝试了什么?我首先按如下方式检索我的授权令牌:

public String getAuthToken() {
        try {
            CloseableHttpClient client = HttpClients.createDefault();
            HttpPost loginPost =  new HttpPost("https://login.microsoftonline.com/organizations/oauth2/v2.0/token");
            String clientId = "some client UUID";
            String scopes = "email openid IMAP.AccessAsUser.All offline_access";
            String client_secret = "My client secret, not base64 encoded";
            String username = "my emailadress";
            String password = "my password, not base64 encoded";

           String encodedBody = "client_id=" + clientId
                    + "&scope=" + scopes
                    + "&client_secret=" + client_secret
                    + "&username=" + username
                    + "&password=" + password
                    + "&grant_type=password";

            loginPost.setEntity(new StringEntity(encodedBody, ContentType.APPLICATION_FORM_URLENCODED));

            loginPost.addHeader(new BasicHeader("cache-control", "no-cache"));
            CloseableHttpResponse loginResponse = client.execute(loginPost);
            byte[] response = loginResponse.getEntity().getContent().readAllBytes();
            ObjectMapper objectMapper = new ObjectMapper();
            JavaType type = objectMapper.constructType(objectMapper.getTypeFactory()
                    .constructParametricType(Map.class, String.class, String.class));
            Map<String, String> parsed = new ObjectMapper().readValue(response, type);
            return parsed.get("access_token");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

来自 oauth 服务的响应实际上是一个 json 对象,其中包含以下字段:

显然令牌更长,但这里不共享。 access_token 本身采用三个 base64 编码字符串的形式,以 .第一个,解码时包含

{
  "typ": "JWT",
  "nonce": "Vobb8bI7E...",
  "alg": "RS256",
  "x5t": "2ZQpJ3Up...",
  "kid": "2ZQpJ3Up..."
}

第二部分是一个更大的对象,包含以下字段(也经过编辑):

{
  "aud": "someuuid",
  "iss": "https://sts.windows.net/someuuid/",
  "iat": 1658397625,
  "nbf": 1658397625,
  "exp": 1658402597,
  "acct": 0,
  "acr": "1",
  "aio": "ASQ....",
  "amr": [
    "pwd"
  ],
  "app_displayname": "myapp",
  "appid": "some uuid",
  "appidacr": "1",
  "family_name": "My Last Name",
  "given_name": "My First Name",
  "idtyp": "user",
  "ipaddr": "some.ip.address.here",
  "name": "My Full name",
  "oid": "someuuid",
  "platf": "14",
  "puid": "10032...",
  "rh": "0.AToA....",
  "scp": "email IMAP.AccessAsUser.All openid profile",
  "sub": "enaKK...",
  "tenant_region_scope": "EU",
  "tid": "someuuid",
  "unique_name": "my email",
  "upn": "my email",
  "uti": "1cc...",
  "ver": "1.0",
  "wids": [
    "some uuid",
    "some uuid"
  ],
  "xms_st": {
    "sub": "02n7h..."
  },
  "xms_tcdt": 1571393936
}

最后一部分只是二进制数据。我目前只是将整个 access_token 传递给 JavaMail,如下所示:

        String accesstoken = new OauthTokenFetcher().getAuthToken();
        imapReader =  new ImapMailBoxReader(
                "outlook.office365.com",
                "my email",
                accesstoken);
        LocalDate startDate = LocalDate.of(2022,4,1);
        LocalDate endDate = LocalDate.of(2022,7,1);
        imapReader.processOnMessages("Inbox", startDate, endDate,this::processMessage);

与 ImapMailBoxReader 如下:

public class ImapMailBoxReader {

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

    public ImapMailBoxReader(String host, String username, String password) {
        this.host = host;
        this.username = username;
        this.password = password;
    }
    public void processOnMessages(String folder, LocalDate since, LocalDate until, Consumer<Message> mailconsumer) {
        try {
            System.out.println("Password:" + password);
            Properties prop = new Properties();
            MailSSLSocketFactory sf = new MailSSLSocketFactory();
            sf.setTrustAllHosts(true);

            prop.put("mail.debug.auth", "true");
            prop.put("mail.imap.sasl.enable", "true");
            prop.put("mail.imap.sasl.mechanisms", "XOAUTH2");
            prop.put("mail.imap.auth.login.disable", "true");
            prop.put("mail.imap.auth.plain.disable", "true");
            prop.put("mail.imap.ssl.enable", "true");

            // Create the session
            //Connect to the server
            Session session = Session.getDefaultInstance(prop, null);
            session.setDebug(true);
            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();
        }
    }

上述语句在 store.connect 语句处失败,并显示 AUTHENTICATE FAILED。 我可能错误地传递了令牌?上面的 JavaMail 文档指出我不应该对令牌进行 Base64 编码,但我确实收到了它。我应该只发送一部分吗?那是哪一部分呢?

任何帮助,将不胜感激。


在一位没有 stackoverflow 帐户的同事的提示下,我终于让它工作了。关键是我用于 OAuth 令牌的范围显然不允许应用程序使用。这段信息隐藏在底部this https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth page.

总结一下,解决办法就是:

  • 您必须配置 IMAP.AccessAsApp 权限,而不是 IMAP.AccessAsUser.All 。此权限无法在与 AccessAsUser.All 权限相同的位置找到,但隐藏在“Office 365 Exchange Online”权限下。
  • 与您期望的不同,您必须使用https://outlook.office365.com/.default访问令牌请求的正文有效负载的范围。

这就成功了。可笑的是,我使用搜索引擎在文档页面中找到这些信息有多么困难。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JavaMail 与 Oauth 和 Office365 的相关文章

随机推荐

  • FQL 图形 API:共同的朋友

    我知道您无法找到朋友的朋友 但是您可以使用某些查询以计算有效的方式找到共同的朋友吗 我什至可以遍历我的朋友来查看他们中哪些是有目标的朋友吗 有什么好的办法可以交到朋友吗 你可以这样做 facebook new Facebook array
  • 我可以像对待数组一样对待结构吗?

    我有一个用于保存 4D 矢量的结构 struct float x float y float z float w vector4f 我正在使用一个库 该库具有一些对向量进行操作但以浮点指针作为参数的函数 调用类似的东西是否合法doSomet
  • 如何使用 Groovy 添加 XML 属性?

    我需要将 属性添加到 Groovy 中 XML 片段的根元素 我想用XmlSlurper 怎么做 添加元素很容易 在 Groovy 控制台中运行此命令以验证其是否有效 import groovy xml StreamingMarkupBui
  • 有没有办法通过 MVC 应用程序中的特定控制器操作来提供 Blazor 应用程序?

    我想在现有的 ASP NET Core 3 0 MVC 项目中设置一个控制器操作来为 Blazor 应用程序提供服务 我过去见过与其他 SPA 框架类似的东西 操作的视图仅包含包含捆绑包的脚本标记 通常还有一些其他设置 例如将 SPA 构建
  • Prometheus Java 摘要指标是线程安全的吗?

    普罗米修斯是Java吗Summary对象线程安全 如果我在类中将其声明为静态 则该类的所有实例都将使用它 Prometheus 是否为该度量对象实现了线程安全 Prometheus 客户端库负责处理方向检测的线程安全等细节 例如Summar
  • is_numeric() 与 is_float() 与 is_int()

    我的理解是 if is numeric input true 那么要么 is float input true OR is int input true OR input 0 OR input是一个数字字符串 意味着如果没有用引号括起来 它
  • FFMPEG 在视频末尾添加图像

    我需要使用 FFMPEG 在 mp4 视频文件末尾添加一秒钟的图像 我的视频尺寸是 WxH 图像尺寸是 MxM 因此视频和图像尺寸不同 我尝试了不同的选项 以便在视频末尾添加图像 ffmpeg i concat videoIn mp4 im
  • iOS 将照片保存在应用程序特定的相册中

    我正在创建一个 iOS 5 应用程序 我想将照片保存到设备中 我想将照片保存到我的应用程序特定的相册中 因此我需要创建相册 然后将照片保存到相册中 我知道如何创建相册 ALAssetsLibrary library ALAssetsLibr
  • R:加速“group by”操作

    我有一个模拟 中间有一个巨大的聚合和组合步骤 我使用 plyr 的 ddply 函数对这个过程进行了原型设计 它可以很好地满足我的大部分需求 但我需要更快的聚合步骤 因为我必须运行 10K 次模拟 我已经在并行扩展模拟 但如果这一步骤更快
  • 设计:新错误(可加密)

    我已经一周没有碰过我的代码了 但是当我捆绑并尝试运行我的网络服务器时 我现在收到以下错误 这让我死在了水里 按照错误输出中的建议包含可设计加密的 gem 并不能解决问题 并且仍然会导致相同的错误 任何帮助是极大的赞赏 DEVISE Devi
  • Reactjs:使用 shouldComponentUpdate() 停止在特定状态更改时重新渲染

    我的组件 onload 中有多个 setState 我的页面在单击下拉值时重新呈现 因为单击时我通过以下方式存储这些值setState 为了停止点击时重新渲染 我使用下面的代码 shouldComponentUpdate return fa
  • Nginx 反向代理配置

    我在使用 nginx 进行简单配置时遇到问题 我有一台托管 docker 容器的服务器 因此 nginx 位于容器中 所以我们调用 urlfoo com 我想要网址foo com service1实际上只是去另一个端口上的 foo com
  • CSS 中如何使用“大于”或“>”字符?

    我在 CSS 文件中多次看到这个字符 但我不知道它是如何使用的 谁能向我解释一下并展示它们如何使页面样式变得更容易 这是一个 CSS 子选择器 P gt SPAN表示将以下样式应用于作为子级的所有 SPAN 标记P tag 请注意 孩子 的
  • Visual Studio Code 中是否有像 DocBlockr 这样的代码注释功能?

    我使用 Sublime Text 3 Atom io 和 Bracket io 作为以前的编辑器 我也非常喜欢新的 Mac 版 Visual Studio 代码编辑器 Sublime 有类似 DocBlockr 的代码注释功能吗 这对我来说
  • Android Studio 0.5.9错误代码42

    每次我尝试从 eclipse 导入项目时 我都会收到这样的错误 Error Execution failed for task app mergeDebugResources 错误 无法运行命令 D Android sdk build to
  • 如何优化间接基数排序? (又名如何优化不可预测的内存访问模式)

    我用 C 编写了一个间接基数排序算法 间接 我的意思是它返回项目的索引 include
  • Kubernetes TLS 秘密证书过期

    我使用 openssl 创建通配符自签名证书 我将证书有效期设置为 到十年 我通过使用 openssl 检查证书来仔细检查有效期 我使用步骤 1 中准备的私钥和证书创建一个 Kubernetes 密钥 如下所示kubectl命令 kubec
  • 如何从ocaml列表中获取子列表

    我正在查看列表文档 图书馆好像没有提供sublist功能 我正在尝试从中获取元素列表i to j 现在我必须把它写成 let rec sublist list i j if i gt j then else List nth list i
  • 如何解析会导致非法 C# 标识符的 JSON 字符串?

    我一直在使用NewtonSoft JSON 转换 http james newtonking com json用于解析 JSON 字符串并将其转换为 C 对象的库 但现在我遇到了一个非常尴尬的 JSON 字符串 我无法将其转换为 C 对象
  • JavaMail 与 Oauth 和 Office365

    我正在用 Java 构建一个简单的命令行应用程序 它可以登录我的电子邮件箱 IMAP 并下载所有附件 我使用了基本身份验证 但 Microsoft 正在禁用它 因此我尝试将我的应用程序转换为使用 OAuth 在阅读了不同的 OAuth 流程