2017-08-07 227 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"); 
     } 

    }