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

Java 1.8 OAuth2连接Office 365 SMTP发件认证失败求助

问题描述

使用Java 1.8 + Jakarta Email 2.1.0,通过OAuth认证连接Microsoft 365 SMTP服务发邮件,必须使用OAuth而非基础认证。已完成以下配置:

Azure AD权限配置

Microsoft Graph    offline_access
Microsoft Graph    User.Read
Microsoft Graph    Mail.Send
Microsoft Graph    openid
Microsoft Graph    IMAP.AccessAsUser.All
Microsoft Graph    SMTP.Send

Office 365 Exchange Online    full_access_as_app
Office 365 Exchange Online    POP.AccessAsApp
Office 365 Exchange Online    Mail.Send
Office 365 Exchange Online    IMAP.AccessAsApp

SMTP客户端认证状态

通过Azure PowerShell确认已开启:

PS C:\Users\dx-2102> Get-TransportConfig | Format-List SmtpClientAuthenticationDisabled   
SmtpClientAuthenticationDisabled : False

已实现的Java代码

SMTP会话配置与发送逻辑

Properties prop = new Properties();
prop.put("mail.smtp.auth", "true");
prop.put("mail.smtp.starttls.enable", "true");
prop.put("mail.smtp.host", emailSettings.getSmtp().getHostname());
prop.put("mail.smtp.port", emailSettings.getSmtp().getPort());
prop.put("mail.debug", "true");
prop.put("mail.debug.auth", "true");
prop.put("mail.smtp.auth.xoauth2.disable", "false");
prop.put("mail.smtp.auth.mechanisms", "XOAUTH2");
prop.put("mail.transport.protocol", "smtp");
prop.put("mail.smtp.auth.login.disable", "true");
prop.put("mail.smtp.auth.plain.disable", "true");

session = Session.getInstance(prop);
session.setDebug(true);
String accessToken = getOAuth2AccessToken();

transport = session.getTransport("smtp");
transport.connect(emailSettings.getSmtp().getHostname(), emailSettings.getSmtp().getPort(), emailSettings.getSmtp().getUsername(), tokenForSmtp(emailSettings.getSmtp().getUsername(), accessToken));
/* -- */ 
transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients());
transport.close();

Azure AD令牌获取方法

String url = "https://login.microsoftonline.com/" + Tenant_ID + "/oauth2/token";

MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", "client_credentials");
map.add("response_type", "code");
map.add("client_id", ClientId);
map.add("client_secret", ClientSecret);
map.add("scope","openid offline_access https%3A%2F%2Foutlook.office365.com%2FSMTP.Send ");

RestTemplate restTemplate = new RestTemplate();
ResponseEntity<AzureResponse> response = restTemplate.postForEntity(url, map, AzureResponse.class);

SMTP令牌处理方法

private String tokenForSmtp(String userName, String accessToken) {
    final String ctrlA=Character.toString((char) 1);
    final String coded= "user=" + userName + ctrlA+"auth=Bearer " + accessToken + ctrlA+ctrlA;
    return Base64.getEncoder().encodeToString(coded.getBytes());
    //base64("user=" + userName + "^Aauth=Bearer " + accessToken + "^A^A")
}

错误信息

AUTH XOAUTH2 dXNlcj1zb2ZhQHNvbHV0aW9uZmFjdG9yeWFnLm9ub...=
535 5.7.3 Authentication unsuccessful [VI1PR0202CA0024.eurprd02.prod.outlook.com]
Error on sending email: 535 5.7.3 Authentication unsuccessful [VI1PR0202CA0024.eurprd02.prod.outlook.com]

疑问:除了openid、offline_access和https://outlook.office.com/SMTP.Send之外,是否还需要其他Azure AD令牌权限?Azure配置是否有遗漏?求正确Java实现示例及Azure账户配置截图。


问题排查与解决方案

核心问题点

  1. 权限类型不匹配
    你使用client_credentials(客户端凭证)模式,但当前配置的SMTP.Send是委托权限(依赖用户上下文),该模式需要配置应用权限。
  2. 令牌请求Scope错误
    客户端凭证模式下,SMTP的正确Scope应为https://outlook.office365.com/.default,而非指定单个SMTP.Send——应用权限必须通过/.default获取已配置的权限集合。
  3. 冗余的令牌编码
    Jakarta Email 2.1.0原生支持XOAUTH2认证,无需手动编码tokenForSmtp,直接传入accessToken即可,手动编码易出现控制字符错误。

修正步骤

1. Azure AD权限调整

  • 删除Microsoft Graph的SMTP.Send委托权限,添加Office 365 Exchange Online的SMTP.Send应用权限(类型为Application)。
  • 点击授予管理员同意,确保权限状态为“已同意”。
  • 移除openid和offline_access,这两个是委托权限模式专属,客户端凭证模式无需配置。

2. 修正令牌请求代码

String url = "https://login.microsoftonline.com/" + Tenant_ID + "/oauth2/v2.0/token"; // 建议使用v2.0端点

MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", "client_credentials");
map.add("client_id", ClientId);
map.add("client_secret", ClientSecret);
map.add("scope", "https://outlook.office365.com/.default"); // 修正Scope

RestTemplate restTemplate = new RestTemplate();
ResponseEntity<AzureResponse> response = restTemplate.postForEntity(url, map, AzureResponse.class);

3. 修正SMTP连接代码

移除手动编码逻辑,直接传入accessToken:

// 去掉tokenForSmtp方法调用,直接传accessToken
transport.connect(emailSettings.getSmtp().getHostname(), emailSettings.getSmtp().getPort(), emailSettings.getSmtp().getUsername(), accessToken);

4. 验证SMTP端点

确保SMTP主机为smtp.office365.com,端口为587(STARTTLS)。

正确Java实现示例

import jakarta.mail.*;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.util.Properties;

public class Office365SmtpOAuthSender {
    private static final String TENANT_ID = "你的租户ID";
    private static final String CLIENT_ID = "你的客户端ID";
    private static final String CLIENT_SECRET = "你的客户端密钥";
    private static final String SMTP_HOST = "smtp.office365.com";
    private static final int SMTP_PORT = 587;
    private static final String SENDER_EMAIL = "sender@yourdomain.com";

    public static void main(String[] args) throws Exception {
        // 获取OAuth2令牌
        String accessToken = getAccessToken();

        // 配置SMTP会话
        Properties props = new Properties();
        props.put("mail.smtp.auth", "true");
        props.put("mail.smtp.starttls.enable", "true");
        props.put("mail.smtp.host", SMTP_HOST);
        props.put("mail.smtp.port", SMTP_PORT);
        props.put("mail.smtp.auth.mechanisms", "XOAUTH2");
        props.put("mail.debug", "true");

        Session session = Session.getInstance(props);
        Transport transport = session.getTransport();

        try {
            // 连接SMTP服务器
            transport.connect(SMTP_HOST, SMTP_PORT, SENDER_EMAIL, accessToken);

            // 创建邮件
            MimeMessage message = new MimeMessage(session);
            message.setFrom(new InternetAddress(SENDER_EMAIL));
            message.addRecipient(Message.RecipientType.TO, new InternetAddress("recipient@example.com"));
            message.setSubject("Test Email via OAuth2");
            message.setText("This is a test email sent using OAuth2 authentication with Office 365 SMTP.");

            // 发送邮件
            transport.sendMessage(message, message.getAllRecipients());
            System.out.println("Email sent successfully.");
        } finally {
            transport.close();
        }
    }

    private static String getAccessToken() {
        String tokenUrl = "https://login.microsoftonline.com/" + TENANT_ID + "/oauth2/v2.0/token";

        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("grant_type", "client_credentials");
        params.add("client_id", CLIENT_ID);
        params.add("client_secret", CLIENT_SECRET);
        params.add("scope", "https://outlook.office365.com/.default");

        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<AzureTokenResponse> response = restTemplate.postForEntity(tokenUrl, params, AzureTokenResponse.class);

        assert response.getBody() != null;
        return response.getBody().getAccessToken();
    }

    // 令牌响应实体类
    private static class AzureTokenResponse {
        private String access_token;

        public String getAccessToken() {
            return access_token;
        }

        public void setAccessToken(String access_token) {
            this.access_token = access_token;
        }
    }
}

Azure配置要点(文字说明)

  1. 应用注册:在Azure AD中注册服务器端/守护程序应用(无需配置重定向URI)。
  2. API权限:
    • 添加Office 365 Exchange Online → SMTP.Send(应用权限)。
    • 点击“授予管理员同意”,确认权限状态为“已同意”。
  3. 客户端密钥:在“证书和密码”页面生成客户端密钥,保存密钥值(仅显示一次)。
  4. 邮箱单独配置:确保目标邮箱未单独禁用SMTP客户端认证,可执行PowerShell命令:Set-CASMailbox -Identity "user@domain.com" -SmtpClientAuthenticationDisabled $false。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 16:55:35