You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaMail使用OAuth2.0连接Outlook邮箱认证失败求助

问题描述

原本通过JavaMail的PLAIN认证(用户名密码)可正常连接Outlook邮件服务器,因微软即将禁用基础认证,计划升级至OAuth2.0。已通过客户端凭证流获取到Token,但用该Token替代密码进行IMAP连接时认证失败。

Java代码

获取Token的代码

public String getAccessTokenByClientCredentialGrant()  {
    String accessToken = null;
    final String clientId = AZURE_CLIENT_ID; // "<client id from azure app registration>"
    final String secret = AZURE_CLIENT_SECRET_VALUE;  // "<client secret from azure app registration>" - client secret value
    final String authority = "https://login.microsoftonline.com/"+AZURE_TENANT_ID+"/oauth2/v2.0/token"; // "https://login.microsoftonline.com/<tenant-id from azure>/oauth2/v2.0/token"; or https://login.microsoftonline.com/{tenant}/v2.0/adminconsent?client_id=<CLIENT_ID>&redirect_uri=<REDIRECT_URI>&scope=https://ps.outlook.com/.default
    final String scope = "https://outlook.office365.com/.default";  // "https://ps.outlook.com/.default";

    try {
        ConfidentialClientApplication app = ConfidentialClientApplication.builder(clientId, ClientCredentialFactory.createFromSecret(secret)).authority(authority).build();
        // With client credentials flows the scope is ALWAYS of the shape "resource/.default", as the application permissions need to be set statically (in the portal), and then granted by a tenant administrator
        ClientCredentialParameters clientCredentialParam = ClientCredentialParameters.builder(Collections.singleton(scope)).build();

        CompletableFuture<IAuthenticationResult> future = app.acquireToken(clientCredentialParam);
        IAuthenticationResult result = future.get();
        accessToken = result.accessToken();
        if (StringUtils.isBlank(accessToken)) {
            logger.error("Access token: "+accessToken);
        }
    } catch(Exception e) {
        logger.error("Exception in acquiring token: "+e.getMessage(), e);
    }
    logger.debug("Access Token : "+accessToken);
    return accessToken;
}

连接邮箱失败的代码

public Store connect(String userEmailId, String oauth2AccessToken) throws Exception {
    Store store = null;
    final String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
    Properties props = new Properties();
    props.put("mail.imaps.ssl.enable", "true");
    props.put("mail.imaps.sasl.enable", "true");
    props.put("mail.imaps.port", port);
    props.put("mail.imaps.host", host);
    props.put("mail.imaps.protocol", "imap");
    props.put("mail.imaps.user", userEmailId);
    props.put("mail.imaps.auth.mechanisms", "XOAUTH2");
    props.put("mail.imaps.sasl.mechanisms", "XOAUTH2");
    props.put("mail.imaps.auth.login.disable", "true");
    props.put("mail.imaps.auth.plain.disable", "true");
    props.setProperty("mail.imaps.socketFactory.class", SSL_FACTORY);
    props.setProperty("mail.imaps.socketFactory.fallback", "true");
    props.setProperty("mail.imaps.socketFactory.port", port);
    props.setProperty("mail.imaps.starttls.enable", "true");
    props.put("mail.debug", "true");
    props.put("mail.debug.auth", "true");

    Session session = Session.getInstance(props);
    session.setDebug(true);
    try {
        store = session.getStore("imaps");
        logger.info("OAUTH2 IMAP ["+store.toString()+"] connect with system properties to Host:" + host + ", Port: "+ port + ", userEmailId: " + userEmailId+ ", OAuth2AccessToken: " + oauth2AccessToken);
        Integer iPort = Integer.parseInt(port);
        store.connect(host, iPort, userEmailId, oauth2AccessToken);
        logger.info("OAUTH2 IMAP connected with system properties to Host:" + host + ", Port: "+ port + ", userEmailId: " + userEmailId+ ", OAuth2AccessToken: " + oauth2AccessToken);
        if(store.isConnected()){
            logger.info("Connection Established using imap protocol successfully !");
        } else {
            logger.info("Connection not Established using imap protocol");
        }
    } catch (Exception e) {
        logger.error("Store.Connect failed with the error: "+e.getMessage());
        StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw));
        String exceptionAsString = sw.toString();
        logger.error(exceptionAsString);
    }
    return store;
}

调试输出

连接时收到认证失败错误:

DEBUG: JavaMail version 1.6.2
DEBUG: successfully loaded resource: /META-INF/javamail.default.address.map
DEBUG: setDebug: JavaMail version 1.6.2
DEBUG: getProvider() returning javax.mail.Provider[STORE,imaps,com.sun.mail.imap.IMAPSSLStore,Oracle]
DEBUG IMAPS: mail.imap.fetchsize: 16384
DEBUG IMAPS: mail.imap.ignorebodystructuresize: false
DEBUG IMAPS: mail.imap.statuscachetimeout: 1000
DEBUG IMAPS: mail.imap.appendbuffersize: -1
DEBUG IMAPS: mail.imap.minidletime: 10
DEBUG IMAPS: enable STARTTLS
DEBUG IMAPS: enable SASL
DEBUG IMAPS: SASL mechanisms allowed: XOAUTH2
DEBUG IMAPS: closeFoldersOnStoreFailure

DEBUG IMAPS: trying to connect to host "outlook.office365.com", port 993, isSSL true
* OK The Microsoft Exchange IMAP4 service is ready. [xxx==]
A0 CAPABILITY
* CAPABILITY IMAP4 IMAP4rev1 AUTH=PLAIN AUTH=XOAUTH2 SASL-IR UIDPLUS ID UNSELECT CHILDREN IDLE NAMESPACE LITERAL+
A0 OK CAPABILITY completed.
DEBUG IMAPS: AUTH: PLAIN
DEBUG IMAPS: AUTH: XOAUTH2
DEBUG IMAPS: protocolConnect login, host=outlook.office365.com, user=powwow@company.com, password=<non-null>
DEBUG IMAPS: SASL Mechanisms:
DEBUG IMAPS:  XOAUTH2
DEBUG IMAPS:
DEBUG IMAPS: SASL client XOAUTH2
DEBUG IMAPS: SASL callback length: 2
DEBUG IMAPS: SASL callback 0: javax.security.auth.callback.NameCallback@2ec9d28c
DEBUG IMAPS: SASL callback 1: javax.security.auth.callback.PasswordCallback@7a98e6b5
A1 AUTHENTICATE XOAUTH2 xxx
A1 NO AUTHENTICATE failed.

javax.mail.AuthenticationFailedException: AUTHENTICATE failed.
    at com.sun.mail.imap.IMAPStore.protocolConnect(IMAPStore.java:732)

已做配置

  • 通过PowerShell为邮箱分配FULLACCESS权限:
PS /Users/richardmarais> Install-Module -Name ExchangeOnlineManagement -allowprerelease

PS /Users/richardmarais> Import-module ExchangeOnlineManagement                        

PS /Users/richardmarais> Connect-ExchangeOnline -Organization <Directory (tenant) ID>

New-ServicePrincipal -AppId <APPLICATION_ID> -ServiceId <OBJECT_ID> [-Organization <ORGANIZATION_ID>]

Get-ServicePrincipal | fl

Add-MailboxPermission -Identity "powwow@company.com" -User 
<SERVICE_PRINCIPAL_ID> -AccessRights FullAccess

Identity             User                 AccessRights                                                                                                                                                                                         IsInherited Deny

--------             ----                 ------------                                                                                                                                                                                         ----------- ----

powwow               ZAFPxxxxx2\$MIxx… {FullAccess} 
  • 在Azure中配置了对应的API权限,token中显示包含IMAP.AccessAsApp等权限。

问题更新

尝试调整scope为https://graph.microsoft.com/.default仍报错;使用user.read.all则触发“AADSTS1002012”错误,提示客户端凭证流的scope需以/.default结尾。目前即使token包含正确权限,IMAP认证仍失败。

解决思路
  1. 校验Token的受众与权限
    确保获取Token时的scope为https://outlook.office365.com/.default,用JWT解析工具检查Token的aud字段是https://outlook.office365.com,roles字段包含IMAP.AccessAsApp。同时确认Azure应用注册中已添加Office 365 Exchange Online的IMAP.AccessAsApp应用权限,且管理员已授予租户级同意。

  2. 确认Service Principal权限生效
    执行Get-MailboxPermission -Identity "powwow@company.com",检查返回结果中Service Principal的Object ID是否对应,且AccessRights包含FullAccess。权限配置后可能需要等待15-30分钟同步到Exchange Online。

  3. 优化JavaMail配置

    • 删除冗余配置:mail.imaps.protocol、mail.imaps.socketFactory相关字段在JavaMail 1.6+中已无需手动设置,依赖默认SSL逻辑即可。
    • 添加授权ID配置:在props中加入props.put("mail.imaps.sasl.authorizationid", userEmailId);,显式指定授权用户邮箱。
  4. 升级JavaMail版本
    JavaMail 1.6.2对XOAUTH2的SASL认证存在兼容性问题,升级至1.6.5或更高版本可修复已知bug。

  5. 检查邮箱IMAP状态
    执行Get-CASMailbox -Identity "powwow@company.com",确认ImapEnabled字段为True,确保邮箱未禁用IMAP协议。

内容的提问来源于stack exchange,提问作者Richard

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 05:25:51