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

使用服务账户调用Gmail API SMTP发件时遇400错误求助

排查Gmail SMTP OAuth2认证400错误问题

我正尝试使用javax.mail结合Gmail SMTP配置发送邮件,采用Gmail API的OAuth2认证方式,操作步骤如下:

  • 步骤1:使用服务账户文件生成AccessToken;
  • 步骤2:将生成的AccessToken用于XOAUTH2认证以发送邮件。

但执行代码后持续收到异常,Base64解码后内容为:{"status":"400","schemes":"Bearer","scope":"https://mail.google.com/"}。以下是完整代码及执行日志,麻烦帮忙排查是否遗漏操作步骤。


File 1: GoogleServiceAccountAuthentication.java

public class GoogleServiceAccountAuthentication {
 private static final Logger logger = LogManager.getLogger(GoogleServiceAccountAuthentication.class);
 @Value("${proxy.host}")
 private String proxyHost;
 @Value("${proxy.port}")
 private int proxyPort;
 private GoogleCredentials googleCredentials;
 private void getGoogleCredentails(String jsonContent) {
 try {
 JSONObject serviceAccountKey = new JSONObject(
 jsonContent);
 if (proxyHost != null && proxyPort > 0) {
 Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
 HttpTransport httpTransport = new NetHttpTransport.Builder().setProxy(proxy).build();
 HttpTransportFactory httpTransportFactory = () -> httpTransport;
 this.googleCredentials = GoogleCredentials
 .fromStream(
 IOUtils.toInputStream(serviceAccountKey.toString(), "UTF-8"), httpTransportFactory)
 .createScoped(Collections.singleton(GmailScopes.MAIL_GOOGLE_COM));
 } else {
 // If proxy not required
 this.googleCredentials = GoogleCredentials
 .fromStream(
 IOUtils.toInputStream(serviceAccountKey.toString(), "UTF-8"))
 .createScoped(Collections.singleton("https://mail.google.com/"));
 }
 // We are catching the exception because in BOT, user can have multiple NALP configured,
 // so one faulty NALP configuration should not stop others
 } catch (JSONException jsonexception) {
 logger.error("The serviceAccountKey content is not a valid json", jsonexception);
 } catch (IOException ie) {
 logger.error("Error while creating GoogleCredentials", ie);
 }
 logger.debug("creation of googleCredentials object is successful");
 }
 public String getAccessToken(String jsonContent) throws IOException {
 getGoogleCredentails(jsonContent);
 // This will check if accesstoken is expired and refresh it
 this.googleCredentials.refreshIfExpired();
 logger.debug("Preparing to get accesstoken");
 return this.googleCredentials.getAccessToken().getTokenValue();
 }
}

File 2: GmailSMTPCheck.java

public class GmailSMTPCheck {
 private static final String SMTP_SERVER_HOST = "smtp.gmail.com";
 private static final String SMTP_SERVER_PORT = "587";
 private static final String SUBJECT = "Sending mail with Gmail SMTP and Java Mail";
 private static final String BODY = "Hi,<br><br>This is a programmatic email.";
 public static void main(String[] args) throws Exception {
 GmailSMTPCheck gmailSMTPCheck = new GmailSMTPCheck();
 GoogleServiceAccountAuthentication googleServiceAccountAuthentication = new GoogleServiceAccountAuthentication();
 JSONObject jsonContent = new JSONObject(
 "<<ServiceAccount_JSON_STRING>>");
 final String FROM_USER_EMAIL = "<<Enter gmail id>>";
 final String FROM_USER_FULLNAME = "<<Enter gmail id>>";
 final String FROM_USER_ACCESSTOKEN = googleServiceAccountAuthentication.getAccessToken(jsonContent.toString());
 System.out.println("The accesstoken is : " + FROM_USER_ACCESSTOKEN);
 final String TO_USER_EMAIL = "<<Enter To user gmail id>>";
 gmailSMTPCheck.sendMail(SMTP_SERVER_HOST, SMTP_SERVER_PORT, FROM_USER_EMAIL, FROM_USER_ACCESSTOKEN, FROM_USER_EMAIL, FROM_USER_FULLNAME, TO_USER_EMAIL, SUBJECT, BODY);
 }
 void sendMail(String smtpServerHost, String smtpServerPort, String smtpUserName, String smtpUserAccessToken, String fromUserEmail, String fromUserFullName, String toEmail, String subject, String body) {
 try {
 Properties props = System.getProperties();
 props.put("mail.transport.protocol", "smtp");
 props.put("mail.smtp.port", smtpServerPort);
 props.put("mail.smtp.starttls.enable", "true");
 props.put("mail.smtp.starttls.required", "true");
 props.put("mail.smtp.sasl.enable", "true");
 props.put("mail.smtp.sasl.mechanisms", "XOAUTH2");
 props.put("mail.imaps.sasl.mechanisms.oauth2.oauthToken", smtpUserAccessToken);
 Session session = Session.getInstance(props);
 session.setDebug(true);
 MimeMessage msg = new MimeMessage(session);
 msg.setFrom(new InternetAddress(fromUserEmail, fromUserFullName));
 msg.setRecipient(Message.RecipientType.TO, new InternetAddress(toEmail));
 msg.setSubject(subject);
 msg.setContent(body, "text/html");
 SMTPTransport transport = new SMTPTransport(session, null);
 transport.connect(smtpServerHost, smtpUserName, null);
 /*
 * transport.issueCommand("AUTH XOAUTH2 " + new String(BASE64EncoderStream.encode(
 * String.format("user=%s\1auth=Bearer %s\1\1", smtpUserName, smtpUserAccessToken)
 * .getBytes())),
 * 235);
 */
 byte[] response = String.format("user=%s\1auth=Bearer %s\1\1", smtpUserName, smtpUserAccessToken).getBytes();
 response = BASE64EncoderStream.encode(response);
 transport.issueCommand("AUTH XOAUTH2 " + new String(response), 235);
 transport.sendMessage(msg, msg.getAllRecipients());
 transport.close();
 } catch (Exception ex) {
 LogManager.getLogger(this.getClass().getName()).log(Level.FATAL, ex.getMessage(), ex);
 System.out.println("the message" + ex.getMessage());
 }
 }
}

执行日志

The accesstoken is : <>
DEBUG: setDebug: JavaMail version 1.6.0
DEBUG SMTP: enable SASL
DEBUG SMTP: useEhlo true, useAuth false
DEBUG SMTP: trying to connect to host "smtp.gmail.com", port 587, isSSL false
220 smtp.gmail.com ESMTP 20sm15725767pfh.72 - gsmtp
DEBUG SMTP: connected to host "smtp.gmail.com", port: 587
EHLO N-20HEPF0V78PV.mshome.net
250-smtp.gmail.com at your service, [131.228.66.30]
250-SIZE 35882577
250-8BITMIME
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-CHUNKING
250 SMTPUTF8
DEBUG SMTP: Found extension "SIZE", arg "35882577"
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: Found extension "STARTTLS", arg ""
DEBUG SMTP: Found extension "ENHANCEDSTATUSCODES", arg ""
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "CHUNKING", arg ""
DEBUG SMTP: Found extension "SMTPUTF8", arg ""
STARTTLS
220 2.0.0 Ready to start TLS
EHLO N-20HEPF0V78PV.mshome.net
250-smtp.gmail.com at your service, [131.228.66.30]
250-SIZE 35882577
250-8BITMIME
250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-CHUNKING
250 SMTPUTF8
DEBUG SMTP: Found extension "SIZE", arg "35882577"
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: Found extension "AUTH", arg "LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH"
DEBUG SMTP: Found extension "ENHANCEDSTATUSCODES", arg ""
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "CHUNKING", arg ""
DEBUG SMTP: Found extension "SMTPUTF8", arg ""
AUTH XOAUTH2 dXNlcj1icG1tYWlsY2hlY2tAYnBtbWFpbGNoZWNrLmlhbS5nc2VydmljZWFjY291bnQuY29tAWF1dGg9QmVhcmVyIHlhMjkuYy5FbGwtWQPTQi05V1Zyd1pmYVBGbTJFQzdub2xwdE1XVkVGSFhMX3liNWFhMnYwTG9vODEzTDNhTHVrakg1ZE9xY2lnUW9zNVVCZ3AxWjVEWjhMOUxPNVptOUk5MXQ5YllkUTJxV0dfNFJObHFnUVdOa3BVZDFCclpfYzRvdwEB
334 eyJzdGF0dXMiOiI0MDAiLCJzY2hlbWVzIjoiQmVhcmVyIiwic2NvcGUiOiJodHRwczovL21haWwuZ29vZ2xlLmNvbS8ifQ==
12:15:13.440 [main] FATAL com.motive.camunda.mail.GmailSMTPCheck - 334 eyJzdGF0dXMiOiI0MDAiLCJzY2hlbWVzIjoiQmVhcmVyIiwic2NvcGUiOiJodHRwczovL21haWwuZ29vZ2xlLmNvbS8ifQ==
javax.mail.MessagingException: 334 eyJzdGF0dXMiOiI0MDAiLCJzY2hlbWVzIjoiQmVhcmVyIiwic2NvcGUiOiJodHRwczovL21haWwuZ29vZ2xlLmNvbS8ifQ==
 at com.sun.mail.smtp.SMTPTransport.issueCommand(SMTPTransport.java:2309) ~[javax.mail-1.6.0.jar:1.6.0]
 at com.motive.camunda.mail.GmailSMTPCheck.sendMail(GmailSMTPCheck.java:86) [classes/:?]
 at com.motive.camunda.mail.GmailSMTPCheck.main(GmailSMTPCheck.java:45) [classes/:?]
the message334 eyJzdGF0dXMiOiI0MDAiLCJzY2hlbWVzIjoiQmVhcmVyIiwic2NvcGUiOiJodHRwczovL21haWwuZ29vZ2xlLmNvbS8ifQ==

问题分析与解决方案

这个400错误的核心原因是服务账户缺少域范围授权(Domain-Wide Delegation),且代码未让服务账户模拟具体用户邮箱发送邮件。以下是具体修复步骤:

一、Google Cloud Console端必须补充的配置

服务账户本身没有独立的Gmail邮箱,要发送邮件必须模拟Google Workspace(原G Suite)域内的用户,需完成以下配置:

  1. 登录Google Cloud Console,进入你的项目,找到目标服务账户的详情页。
  2. 点击编辑按钮,滚动到域范围授权区域,点击添加客户端ID。
  3. 填入服务账户的客户端ID(可从JSON密钥文件的client_id字段获取),并在授权范围中添加https://mail.google.com/,保存配置。
  4. 登录Google Workspace管理后台(admin.google.com),进入安全 > API控制 > 域范围授权,添加上述客户端ID并授权https://mail.google.com/范围。

二、代码修复要点

你的GoogleServiceAccountAuthentication类生成Credentials时,缺少createDelegated()方法指定要模拟的用户邮箱,导致服务账户无法以合法身份发送邮件。修改如下:

修改GoogleServiceAccountAuthentication.java

  1. 调整getGoogleCredentails方法,添加createDelegated():
// 以代理场景为例,修改后的代码片段:
this.googleCredentials = GoogleCredentials
    .fromStream(
        IOUtils.toInputStream(serviceAccountKey.toString(), "UTF-8"), httpTransportFactory)
    .createScoped(Collections.singleton(GmailScopes.MAIL_GOOGLE_COM))
    .createDelegated(fromUserEmail); // 新增这一行,指定要模拟的用户邮箱
  1. 调整getAccessToken方法,接收fromUserEmail参数:
public String getAccessToken(String jsonContent, String fromUserEmail) throws IOException {
    getGoogleCredentails(jsonContent, fromUserEmail); // 传递用户邮箱
    this.googleCredentials.refreshIfExpired();
    logger.debug("Preparing to get accesstoken");
    return this.googleCredentials.getAccessToken().getTokenValue();
}

修改GmailSMTPCheck.java的main方法

调用getAccessToken时传入用户邮箱:

final String FROM_USER_ACCESSTOKEN = googleServiceAccountAuthentication.getAccessToken(jsonContent.toString(), FROM_USER_EMAIL);

三、额外检查项

  • 确保FROM_USER_EMAIL是Google Workspace域内的有效邮箱,普通个人Gmail账号不支持服务账户的域范围授权。
  • 确认服务账户的JSON密钥文件正确无误,且该账户在Google Cloud项目中拥有足够权限(比如Editor权限用于测试)。
  • 检查代理配置是否正常,若代理无法访问Google API,也会导致Token无效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:02:31