使用服务账户调用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)域内的用户,需完成以下配置:
- 登录Google Cloud Console,进入你的项目,找到目标服务账户的详情页。
- 点击编辑按钮,滚动到域范围授权区域,点击添加客户端ID。
- 填入服务账户的客户端ID(可从JSON密钥文件的
client_id字段获取),并在授权范围中添加https://mail.google.com/,保存配置。 - 登录Google Workspace管理后台(admin.google.com),进入安全 > API控制 > 域范围授权,添加上述客户端ID并授权
https://mail.google.com/范围。
二、代码修复要点
你的GoogleServiceAccountAuthentication类生成Credentials时,缺少createDelegated()方法指定要模拟的用户邮箱,导致服务账户无法以合法身份发送邮件。修改如下:
修改GoogleServiceAccountAuthentication.java
- 调整
getGoogleCredentails方法,添加createDelegated():
// 以代理场景为例,修改后的代码片段: this.googleCredentials = GoogleCredentials .fromStream( IOUtils.toInputStream(serviceAccountKey.toString(), "UTF-8"), httpTransportFactory) .createScoped(Collections.singleton(GmailScopes.MAIL_GOOGLE_COM)) .createDelegated(fromUserEmail); // 新增这一行,指定要模拟的用户邮箱
- 调整
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
相关产品推荐
相关产品推荐

