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账户配置截图。
问题排查与解决方案
核心问题点
- 权限类型不匹配
你使用client_credentials(客户端凭证)模式,但当前配置的SMTP.Send是委托权限(依赖用户上下文),该模式需要配置应用权限。 - 令牌请求Scope错误
客户端凭证模式下,SMTP的正确Scope应为https://outlook.office365.com/.default,而非指定单个SMTP.Send——应用权限必须通过/.default获取已配置的权限集合。 - 冗余的令牌编码
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配置要点(文字说明)
- 应用注册:在Azure AD中注册服务器端/守护程序应用(无需配置重定向URI)。
- API权限:
- 添加
Office 365 Exchange Online→SMTP.Send(应用权限)。 - 点击“授予管理员同意”,确认权限状态为“已同意”。
- 添加
- 客户端密钥:在“证书和密码”页面生成客户端密钥,保存密钥值(仅显示一次)。
- 邮箱单独配置:确保目标邮箱未单独禁用SMTP客户端认证,可执行PowerShell命令:
Set-CASMailbox -Identity "user@domain.com" -SmtpClientAuthenticationDisabled $false。
内容的提问来源于stack exchange,提问作者mario
相关产品推荐
相关产品推荐

