2017-08-07 229 views
0

我想在web应用程序(java 1.8/tomcat8)中使用最新的javamail 1.6.0 api代表客户端用户的应用程序发送电子邮件。有些客户使用Gmail。 我不想让他们按照javamail FAQ的建议在他们的谷歌账户中访问不安全的应用程序,并且我愿意实现oauth2,如果这是需要的。Javamail gmail和OAuth2

为了做到这一点,我做了google developer console如下:

  • 创建的oauth2凭证自动创建一个应用程序
  • 授予Gmail的API
  • 应用程序访问的应用程序(客户端ID, clientsecret)

然后,我在我的应用程序中使用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(); 

(其中的代码是返回的授权代码) 这似乎是工作,并返回一个访问令牌和刷新令牌。 (我也可以回到我的谷歌帐户设置,并看到我已授权应用程序发送电子邮件

所以现在我想尝试使用访问令牌通过使用我的gmail用户名的javamail api发送邮件(我登录的授权应用程序中的一个),访问令牌,并进行如下设置:

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=<[email protected]>, 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状态,我解释为表示该标记无效。

我也验证令牌是好的使用谷歌REST API:

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

我也使用启用SSL端口465尝试过,但得到了同样的错误。

我在做什么错在这里?

+0

你尝试重新生成令牌? – Oleg

+0

您将访问令牌(而不是刷新令牌)作为密码传递给JavaMail API,对吧?而且,在传递它之前,您没有以任何方式对令牌进行编码,对吧?该标记应该少于80个字符,并且看起来像“ya29.3QEMCT ...”。您可能希望将mail.debug.auth属性设置为true,以便您可以在调试输出中查看身份验证交换的详细信息,以防服务器提供更多正常调试中所禁止的错误细节输出。你能配置Thunderbird在你的账户中使用OAUTH2吗? –

+0

是的,尝试使用最初生成的身份验证令牌,并尝试刷新它。我确实启用了mail.debug.auth。没有看到更多的输出。 – user2000974

回答

1

这是对user2000974答案的补充。谷歌的有关使用OAuth验证到IMAP或SMTP服务器Gmail > IMAP > OAuth 2.0 Mechanism的文件中明确规定以下

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

IMAP和SMTP访问的范围是https://mail.google.com/

我希望这会引导未来偶然发现这个问题的人到正确的文档页面。

+0

谢谢你的链接!我搜索了三天的文档,但从未想过在IMAP文档下查看有关如何访问SMTP的信息。描述前面引用的所有google oauth2范围的页面没有提及此。\ – user2000974

0

我试着玩所请求的范围,最后通过请求完全访问gmail帐户(范围=“https://mail.google.com/”)来完成它的工作。有限的documentation of the scopes表明发送邮件的具体范围应该可以工作,但显然不适用。要求完全访问帐户的作品,但似乎打败了有限的范围的目的。它开始听起来像SMTP服务器就是不尊重有限的范围。

0

下面给出的是使用Gmail帐户和OAuth2发送电子邮件的工作代码。此代码使用Spring框架。

因为我在和Spring做一个Web项目,所以我决定用Spring来发送邮件。

重要:

  • 首先,在Google console创建项目,之后,当在谷歌创建控制台凭证记得 应用程序类型设置为其他。

  • 使用oauth2.py获取访问令牌和 代码所需的其他所需值。文件本身有指导如何使用它,以获得所需的值。

弹簧email.xml

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 
    <!-- email configuration --> 
    <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl"> 
     <property name="host" value="smtp.gmail.com"/> 
     <property name="port" value="587"/> 
     <property name="username" value="[email protected]"/> 
     <property name="password" value=""/> 

     <property name="javaMailProperties"> 
      <props> 
       <!-- Use SMTP transport protocol --> 
       <prop key="mail.transport.protocol">smtp</prop> 
       <!-- Use SMTP-AUTH to authenticate to SMTP server --> 
       <prop key="mail.smtp.auth">true</prop> 
       <!-- GMail requires OAuth to not be considered "low-security" --> 
       <prop key="mail.smtp.auth.mechanisms">XOAUTH2</prop> 
       <!-- Use TLS to encrypt communication with SMTP server --> 
       <prop key="mail.smtp.starttls.enable">true</prop> 
       <prop key="mail.debug">false</prop> 
      </props> 
     </property> 

    </bean> 
    <bean id="message" class="com.upriverbank.model.Message"> 
     <property name="mailSender" ref="mailSender" /> 
    </bean> 

</beans> 

OAuthMail.java

package com.abc.security; 

import com.fasterxml.jackson.core.type.TypeReference; 
import com.fasterxml.jackson.databind.ObjectMapper; 
import org.springframework.mail.javamail.JavaMailSender; 

import java.io.BufferedReader; 
import java.io.IOException; 
import java.io.InputStreamReader; 
import java.io.PrintWriter; 
import java.net.HttpURLConnection; 
import java.net.URL; 
import java.net.URLEncoder; 
import java.util.HashMap; 

public class OAuthMail { 


    // For using Oauth2 

    private static String TOKEN_URL = "https://www.googleapis.com/oauth2/v4/token"; 
    private JavaMailSender sender; 

    // Not a best practice to store client id, secret and token in source 
    // must be stored in a file. 
    private String oauthClientId = "xxxxxxxxxxxxxxxx.apps.googleusercontent.com"; 
    private String oauthSecret = "xxxxxxxxxxxxxxxxxxxx"; 
    private String refreshToken = "xxxxxxxxxxxxxxxxxxxxxxx"; 
    private static String accessToken = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + 
              "xxxxxxxxxxxxxxxxxxxx"; 
    private long tokenExpires = 1458168133864L; 

    // getters and setters 

    public static String getAccessToken() { 
     return accessToken; 
    } 

    public static void setAccessToken(String accessToken) { 
     accessToken = accessToken; 
    } 


    /* 
    Renew access token if expired 
    */ 
    public void renewToken(){ 

     if(System.currentTimeMillis() > tokenExpires) { 

      try 
      { 
       String request = "client_id="+ URLEncoder.encode(oauthClientId, "UTF-8") 
         +"&client_secret="+URLEncoder.encode(oauthSecret, "UTF-8") 
         +"&refresh_token="+URLEncoder.encode(refreshToken, "UTF-8") 
         +"&grant_type=refresh_token"; 
       HttpURLConnection conn = (HttpURLConnection) new URL(TOKEN_URL).openConnection(); 
       conn.setDoOutput(true); 
       conn.setRequestMethod("POST"); 
       PrintWriter out = new PrintWriter(conn.getOutputStream()); 
       out.print(request); 
       out.flush(); 
       out.close(); 
       conn.connect(); 

       try 
       { 

        HashMap<String,Object> result; 
        result = new ObjectMapper().readValue(conn.getInputStream(), new TypeReference<HashMap<String,Object>>() {}); 
        accessToken = (String) result.get("access_token"); 
        tokenExpires = System.currentTimeMillis()+(((Number)result.get("expires_in")).intValue()*1000); 
       } 
       catch (IOException e) 
       { 
        String line; 
        BufferedReader in = new BufferedReader(new InputStreamReader(conn.getErrorStream())); 
        while((line = in.readLine()) != null) { 
         System.out.println(line); 
        } 
        System.out.flush(); 
       } 
      } 
      catch (Exception e) 
      { 
       e.printStackTrace(); 
      } 
     } 
    } 
} 

Message.java

package com.abc.model; 


import com.fasterxml.jackson.core.type.TypeReference; 
import com.fasterxml.jackson.databind.ObjectMapper; 
import com.upriverbank.security.OAuthMail; 
import org.springframework.mail.MailSender; 
import org.springframework.mail.SimpleMailMessage; 
import org.springframework.mail.javamail.JavaMailSender; 
import org.springframework.mail.javamail.JavaMailSenderImpl; 

import javax.mail.*; 
import javax.mail.internet.InternetAddress; 
import javax.mail.internet.MimeMessage; 
import java.io.BufferedReader; 
import java.io.IOException; 
import java.io.InputStreamReader; 
import java.io.PrintWriter; 
import java.net.HttpURLConnection; 
import java.net.URL; 
import java.net.URLEncoder; 
import java.util.HashMap; 
import java.util.Properties; 


public class Message { 


    public static final String PORT = "465"; 
    private static final String HOST = "smtp.gmail.com"; 
    private final String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory"; 
    private String name; 
    private String customerID; 
    private String email; 
    private int queryType; 
    private int id; 
    private String title; 
    private String rStatus; 
    private String query; 
    private Byte isRead; 
    private int customerId; 
    private String password; 
    private static final String BANK_SUPPORT_EMAIL = "[email protected]"; 


    public static String getBankSupportEmail() { 
     return BANK_SUPPORT_EMAIL; 
    } 

    public int getId() { 
     return id; 
    } 

    public void setId(int id) { 
     this.id = id; 
    } 

    public String getTitle() { 
     return title; 
    } 

    public void setTitle(String title) { 
     this.title = title; 
    } 

    public String getrStatus() { 
     return rStatus; 
    } 

    public void setrStatus(String rStatus) { 
     this.rStatus = rStatus; 
    } 

    public String getQuery() { 
     return query; 
    } 

    public void setQuery(String query) { 
     this.query = query; 
    } 

    public Byte getIsRead() { 
     return isRead; 
    } 

    public void setIsRead(Byte isRead) { 
     this.isRead = isRead; 
    } 

    public int getCustomerId() { 
     return customerId; 
    } 

    public void setCustomerId(int customerId) { 
     this.customerId = customerId; 
    } 


    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 

    public String getCustomerID() { 
     return customerID; 
    } 

    public void setCustomerID(String customerID) { 
     this.customerID = customerID; 
    } 

    public String getEmail() { 
     return email; 
    } 

    public void setEmail(String email) { 
     this.email = email; 
    } 

    public int getQueryType() { 
     return queryType; 
    } 

    public void setQueryType(int queryType) { 
     this.queryType = queryType; 
    } 




    public void viewMessage() { 

    } 

    public void setPassword(String password) { 
     this.password = password; 
    } 



    private MailSender mailSender; 

    public void setMailSender(MailSender mailSender) { 
     this.mailSender = mailSender; 
    } 



    /* 
     Sending an email according to provided parameters 
     (sender, receiver, subject, content) 
    */ 
    public void sendMessage(String from, String to, String subject, String msg) { 


     ((JavaMailSenderImpl)this.mailSender).setPassword(OAuthMail.getAccessToken()); 

     SimpleMailMessage message = new SimpleMailMessage(); 

     message.setFrom(from); 
     message.setTo(to); 
     message.setSubject(subject); 
     message.setText(msg); 

     // sending email 

     mailSender.send(message); 
    } 

} 

MessageTest.java

package com.abc.model; 

import com.abc.support.spring.SpringUtil; 
import org.junit.jupiter.api.Test; 
import org.springframework.context.ApplicationContext; 
import org.springframework.context.support.ClassPathXmlApplicationContext; 

    class MessageTest { 

     @Test 
     void sendMessage() { 

      Message mm = (Message) SpringUtil.customBeanFactory("message"); 
      mm.sendMessage("[email protected]", 
        "[email protected]", 
        "Testing email", 
        "This is a testing email"); 
     } 

    }