Javamail Gmail 和 OAuth2

8
我正在尝试在Web应用程序(Java 1.8 / Tomcat8)中使用最新的Javamail 1.6.0 API代表应用程序的客户用户发送电子邮件,其中一些客户使用Gmail。 我不想像javamail FAQ建议的那样要求他们启用对不安全应用程序的访问,并且我愿意实现oauth2(如果需要的话)。为此,在google developer console上执行以下操作:
  • 创建一个应用程序
  • 为该应用程序创建oauth2凭据(客户端ID,客户端密钥)
  • 授予该应用程序访问Gmail API的权限
然后,我使用google oauth client在我的应用程序中实现了oauth2流程。
授权重定向已构建:
String url = 
        new GoogleAuthorizationCodeRequestUrl(clientId, 
            redirectUrl, 
            Collections.singleton(GmailScopes.GMAIL_SEND)
            ).setAccessType("offline").build();

这成功地重定向到谷歌网站,我可以在那里进行身份验证并授权我的应用程序代表我发送邮件。授权成功后,它成功地重定向回我的应用程序,并处理授权代码:

GoogleTokenResponse response =
                new GoogleAuthorizationCodeTokenRequest(
                        new NetHttpTransport(), 
                        new JacksonFactory(),
                      clientId, 
                      clientSecret,
                      code, 
                      redirectUrl)
                .execute();

(其中 code 是返回的授权码) 这似乎有效,并返回访问令牌和刷新令牌。 (我还可以回到我的 Google 帐户设置中,看到我已经授权应用程序发送电子邮件。)

现在我想尝试使用访问令牌通过 javamail api 发送邮件,使用我的 gmail 用户名(我登录授权应用程序时使用的用户名),访问令牌和以下设置:

host = "smtp.gmail.com";
port = 587;
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.auth.mechanisms", "XOAUTH2");

javamail代码在其他smtp服务器上运行良好。 我还打开了调试来跟踪smtp流

DEBUG: JavaMail version 1.6.0
DEBUG: successfully loaded resource: /META-INF/javamail.default.providers
DEBUG: Tables of loaded providers
DEBUG: Providers Listed By Class Name: {com.sun.mail.smtp.SMTPSSLTransport=javax.mail.Provider[TRANSPORT,smtps,com.sun.mail.smtp.SMTPSSLTransport,Oracle], com.sun.mail.smtp.SMTPTransport=javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Oracle], com.sun.mail.imap.IMAPSSLStore=javax.mail.Provider[STORE,imaps,com.sun.mail.imap.IMAPSSLStore,Oracle], com.sun.mail.pop3.POP3SSLStore=javax.mail.Provider[STORE,pop3s,com.sun.mail.pop3.POP3SSLStore,Oracle], com.sun.mail.imap.IMAPStore=javax.mail.Provider[STORE,imap,com.sun.mail.imap.IMAPStore,Oracle], com.sun.mail.pop3.POP3Store=javax.mail.Provider[STORE,pop3,com.sun.mail.pop3.POP3Store,Oracle]}
DEBUG: Providers Listed By Protocol: {imaps=javax.mail.Provider[STORE,imaps,com.sun.mail.imap.IMAPSSLStore,Oracle], imap=javax.mail.Provider[STORE,imap,com.sun.mail.imap.IMAPStore,Oracle], smtps=javax.mail.Provider[TRANSPORT,smtps,com.sun.mail.smtp.SMTPSSLTransport,Oracle], pop3=javax.mail.Provider[STORE,pop3,com.sun.mail.pop3.POP3Store,Oracle], pop3s=javax.mail.Provider[STORE,pop3s,com.sun.mail.pop3.POP3SSLStore,Oracle], smtp=javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Oracle]}
DEBUG: successfully loaded resource: /META-INF/javamail.default.address.map
DEBUG: getProvider() returning javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Oracle]
DEBUG SMTP: useEhlo true, useAuth true
DEBUG SMTP: trying to connect to host "smtp.gmail.com", port 587, isSSL false
220 smtp.gmail.com ESMTP c7sm3632131pfg.29 - gsmtp
DEBUG SMTP: connected to host "smtp.gmail.com", port: 587

EHLO 10.0.0.5
250-smtp.gmail.com at your service, [216.165.225.194]
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 10.0.0.5
250-smtp.gmail.com at your service, [216.165.225.194]
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 ""
DEBUG SMTP: protocolConnect login, host=smtp.gmail.com, user=<myemail@gmail.com>, password=<non-null>
DEBUG SMTP: Attempt to authenticate using mechanisms: XOAUTH2
DEBUG SMTP: Using mechanism XOAUTH2
DEBUG SMTP: AUTH XOAUTH2 command trace suppressed
DEBUG SMTP: AUTH XOAUTH2 failed, THROW: 


 javax.mail.AuthenticationFailedException: OAUTH2 asked for more
...
DEBUG SMTP: AUTH XOAUTH2 failed
ERROR 2017-08-06 18:39:57,443  - send: 334 eyJzdGF0dXMiOiI0MDAiLCJzY2hlbWVzIjoiQmVhcmVyIiwic2NvcGUiOiJodHRwczovL21haWwuZ29vZ2xlLmNvbS8ifQ==

解码最后一行,我发现错误是400状态,我理解为令牌无效。

我还使用Google REST API验证了令牌是有效的:

https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=<token>

我还尝试使用启用了ssl的465端口,但是出现了相同的错误。

我在这里做错了什么?


1
你尝试重新生成令牌了吗? - Oleg
你确定你正在将访问令牌而不是刷新令牌作为密码传递给JavaMail API吗?并且在传递之前没有以任何方式对令牌进行编码,对吧?该令牌应少于80个字符,并且类似于“ya29.3QEMCT...”。您可能希望将mail.debug.auth属性设置为true,以便您可以在调试输出中查看身份验证交换的详细信息,以防服务器提供了更多在正常调试输出中被抑制的错误详细信息。您能否配置Thunderbird使用您的帐户的OAUTH2? - Bill Shannon
是的,我尝试使用最初生成的身份验证令牌,并尝试刷新它。我已启用mail.debug.auth。但是没有看到更多输出。 - user2000974
啊!我的访问令牌以“ya29.GlugBAd…”开头,但长度为132个字符,并且不同实例的长度似乎略有不同。我直接使用GoogleResponse中的字符串,没有进行其他编码:String at = response.getAccessToken(); - user2000974
...但是我还通过googleapis的tokeninfo方法运行了该令牌,如上所述,并解析该令牌以报告其范围为gmail.send,过期时间在未来,并且它是一个“离线”令牌。 - user2000974
我刚刚也使用Thunderbird进行了测试,它能够通过我的Gmail帐户发送邮件。我注意到它的OAuth授权包含比我在代码中请求的更多范围。我只请求了GMAIL.SEND范围。 - user2000974
3个回答

6
这是对user2000974答案的补充。Google有关使用OAuth进行IMAP或SMTP服务器认证的文档Gmail > IMAP > OAuth 2.0机制清楚地说明了以下内容:

本文档定义了用于与IMAP AUTHENTICATE和SMTP AUTH命令一起使用的SASL XOAUTH2机制。该机制允许使用OAuth 2.0访问令牌对用户的Gmail帐户进行身份验证。

IMAP和SMTP访问权限的范围为https://mail.google.com/

我希望这将引导未来遇到此问题的人进入正确的文档页面。

谢谢你提供的链接!我花了三天时间搜寻文档,但从未想过在 IMAP 文档下查找有关如何访问 SMTP 的信息。我之前参考的描述所有 Google OAuth2 作用域的页面没有提到这一点。 - user2000974
谢谢,这对我有用,可以轻松忽略打印的警告。此外,在某些文档中,它说您应该尽可能使用受限权限,在我的情况下,我只需要发送权限,但对于SMTP,仅发送是不够的。 - MG Developer

0

有一些重要的事情需要注意,那就是 Gmail API 和 Gmail IMAP 不同,尤其是在出现问题时。Gmail API 允许对特定任务进行多个和细粒度的范围设置,但是当使用 Gmail IMAP 时,它将始终需要完整的范围。


0

我尝试使用请求的范围进行操作,最终通过请求对 Gmail 帐户的完全访问权限(scope = "https://mail.google.com/") 来使其正常工作。有限的范围文档表明,发送邮件的特定范围应该可以工作,但显然它并没有。请求对帐户的完全访问权限可以使其正常工作,但似乎违背了具有有限范围的目的。开始听起来像是 SMTP 服务器不尊重有限的范围。


gmail.send仅允许通过Gmail API发送电子邮件,如果要使用SMTP发送,则需要请求完整的Gmail访问权限-https://mail.google.com/。 - ledniov
什么是Gmail API?为什么它需要与JavaMail不同的范围? - eastwater

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接