使用javamail,gmail因应用程序不太安全而拒绝身份validation

我正在运行一个非常基本的Javamail程序来尝试发送电子邮件。 这是带main()的独立程序。 一旦我开始工作,我计划在一个在tomcat下运行的servlet中使用Javamail。

运行此程序时,我收到AUTH LOGIN失败错误。 我尝试了几种不同的属性设置,但都没有解决问题。

然后我在SO上发现了一条post,建议降低我的Google帐户所需的安全级别。 当我降低安全设置时,身份validation成功。

当然,我立即回到Google帐户的更高安全级别。

我的问题是,如何使我的应用程序更安全,以便gmail不拒绝身份validation?

程序代码如下所示。 该程序非常类似于SO上的许多其他Javamail问题中的代码。

TryJavamail.java

import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Properties; import javax.mail.*; import javax.mail.internet.*; public class TryJavamail { public static void main(String args[]) throws MessagingException { String submitName = "John Doe"; String submitEmail = "from@example.com"; String submitMessage = "This is the message"; Properties props = new Properties(); props.put("mail.transport.protocol", "smtp"); props.setProperty("mail.smtp.host", "smtp.gmail.com"); props.setProperty("mail.smtp.auth", "true"); props.setProperty("mail.smtp.ssl.enable", "true"); props.setProperty("mail.smtp.port", "465"); Session session = Session.getInstance(props, null); session.setDebug(true); Message message = new MimeMessage(session); message.setSubject("Message from myapp website submit"); message.setText(submitName + "; " + submitMessage); Address toAddress = new InternetAddress(submitEmail); message.setRecipient(Message.RecipientType.TO, toAddress); Transport transport = session.getTransport("smtp"); transport.connect("smtp.gmail.com", "---userid---", "---password---"); transport.sendMessage(message, message.getAllRecipients()); transport.close(); } } 

您可能想要使用OAuth2身份validation 。

我将我的解决方案作为单独的答案包括在内。 我之前编辑过这个问题,但问题变得太长了。

Servlet使用下面的OAuth2身份validation

下面显示的是一个servlet,它使用OAuth2从我的网站上的“联系人”表单发送电子邮件。 我按照Bill的回答提供的链接中的说明进行操作。

SendMessage.java(需要更复杂的实现,请阅读代码中的注释)

 /* * This program is adapted from sample code provided * by Google Inc at the following location: * https://github.com/google/gmail-oauth2-tools * */ package com.somedomain.servlet; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Properties; import java.util.logging.Logger; import javax.mail.Session; import javax.mail.Message; import javax.mail.Address; import javax.mail.Transport; import javax.mail.URLName; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import javax.mail.internet.InternetAddress; import java.security.Provider; import java.security.Security; import com.sun.mail.smtp.SMTPTransport; import com.somedomain.oauth2.AccessTokenFromRefreshToken; import com.somedomain.oauth2.OAuth2SaslClientFactory; public class SendMessage extends HttpServlet { private static final Logger logger = Logger.getLogger(SendMessage.class.getName()); public static final class OAuth2Provider extends Provider { private static final long serialVersionUIS = 1L; public OAuth2Provider() { super("Google OAuth2 Provider", 1.0, "Provides the XOAUTH2 SASL Mechanism"); put("SaslClientFactory.XOAUTH2", "com.somedomain.oauth2.OAuth2SaslClientFactory"); } } public static void initialize() { Security.addProvider(new OAuth2Provider()); } public static SMTPTransport connectToSmtp(Session session, String host, int port, String userEmail, String oauthToken, boolean debug) throws Exception { final URLName unusedUrlName = null; SMTPTransport transport = new SMTPTransport(session, unusedUrlName); // If the password is non-null, SMTP tries to do AUTH LOGIN. final String emptyPassword = ""; transport.connect(host, port, userEmail, emptyPassword); return transport; } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String submitName = request.getParameter("name"); String submitEmail = request.getParameter("email"); String submitPhone = request.getParameter("phone"); String submitMessage = request.getParameter("message"); try { String host = "smtp.gmail.com"; int port = 587; String userEmail = "---email account used for oauth2---"; String appEmail = "---email account for receiving app emails---"; String oauthToken = ""; initialize(); // // Gmail access tokens are valid for 1 hour. A more sophisticated // implementation would store access token somewhere and reuse it // if it was not expired. A new access token should be generated // only if access token is expired. Abandoning unexpired access // tokens seems wasteful. // oauthToken = AccessTokenFromRefreshToken.getAccessToken(); Properties props = new Properties(); 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(OAuth2SaslClientFactory.OAUTH_TOKEN_PROP, oauthToken); Session session = Session.getInstance(props); session.setDebug(true); SMTPTransport smtpTransport = connectToSmtp(session, host, port, userEmail, oauthToken, true); Message message = new MimeMessage(session); message.setSubject("Submit from somedomain.com website"); message.setText("Name=" + submitName + "\n\nEmail=" + submitEmail + "\n\nPhone=" + submitPhone + "\n\nMessage=" + submitMessage); Address toAddress = new InternetAddress(appEmail); message.setRecipient(Message.RecipientType.TO, toAddress); smtpTransport.sendMessage(message, message.getAllRecipients()); smtpTransport.close(); } catch (MessagingException e) { System.out.println("Messaging Exception"); System.out.println("Error: " + e.getMessage()); } catch (Exception e) { System.out.println("Messaging Exception"); System.out.println("Error: " + e.getMessage()); } String url = "/thankyou.html"; response.sendRedirect(request.getContextPath() + url); } } 

AccessTokenFromRefreshToken.java

 /* * For OAuth2 authentication, this program generates * access token from a previously acquired refresh token. */ package com.somedomain.oauth2; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.util.Map; import java.util.LinkedHashMap; import java.io.DataOutputStream; import java.io.Reader; import java.io.BufferedReader; import java.io.InputStreamReader; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.core.JsonProcessingException; public class AccessTokenFromRefreshToken { public static String getAccessToken() { HttpURLConnection conn = null; String accessToken = null; try { URL url = new URL("https://accounts.google.com/o/oauth2/token"); Map params = new LinkedHashMap<>(); params.put("client_id", "***********.apps.googleusercontent.com"); params.put("client_secret", "****************"); params.put("refresh_token", "*****************"); params.put("grant_type", "refresh_token"); StringBuilder postData = new StringBuilder(); for (Map.Entry param : params.entrySet()) { if (postData.length() != 0) postData.append('&'); postData.append(URLEncoder.encode(param.getKey(), "UTF-8")); postData.append('='); postData.append(URLEncoder.encode(String.valueOf(param.getValue()), "UTF-8")); } byte[] postDataBytes = postData.toString().getBytes("UTF-8"); conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setRequestProperty("Content-Length", String.valueOf(postDataBytes.length)); conn.setRequestProperty("Content-language", "en-US"); conn.setDoOutput(true); DataOutputStream wr = new DataOutputStream ( conn.getOutputStream()); wr.write(postDataBytes); wr.close(); StringBuilder sb = new StringBuilder(); Reader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8")); for ( int c = in.read(); c != -1; c = in.read() ) { sb.append((char)c); } String respString = sb.toString(); // Read access token from json response ObjectMapper mapper = new ObjectMapper(); AccessTokenObject accessTokenObj = mapper.readValue(respString, AccessTokenObject.class); accessToken = accessTokenObj.getAccessToken(); } catch (Exception e) { e.printStackTrace(); } finally { if(conn != null) { conn.disconnect(); } } return(accessToken); } } 

AccessTokenObject.java

 /* * Class that corresponds to the JSON * returned by google OAuth2 token generator */ package com.somedomain.oauth2; import com.fasterxml.jackson.annotation.JsonProperty; public class AccessTokenObject { @JsonProperty("access_token") private String accessToken; @JsonProperty("token_type") private String tokenType; @JsonProperty("expires_in") private int expiresIn; public String getAccessToken() { return accessToken; } public String getTokenType() { return tokenType; } public int getExpiresIn() { return expiresIn; } public void setAccessToken(String accessToken) { this.accessToken = accessToken; } public void setTokenType(String tokenType) { this.tokenType = tokenType; } public void setExpiresIn(int expiresIn) { this.expiresIn = expiresIn; } } 

OAuth2SaslClient.java – 代码在gmail-oauth2-tools中保持不变,但在顶部添加了package语句(package com.somedomain.oauth2;)

OAuth2SaslClientFactory.java – 代码使用不变,添加了package语句

如何让我的应用程序更安全,以便gmail不拒绝身份validation?


我认为一个好的方法是启用双向身份validation,使用 代码中 生成的 应用程序专用密码 替换普通的Gmail密码

 final String smtpServer = "smtp.gmail.com"; final String userAccount = "****@gmail.com"; // Sender Account. final String password = "****"; // Password -> Application Specific Password. final String SOCKET_FACTORY = "javax.net.ssl.SSLSocketFactory"; final String smtpPort = "587"; final String PORT = "465"; final Properties props = new Properties(); props.put("mail.smtp.host", smtpServer); props.put("mail.smtp.user", userAccount); props.put("mail.smtp.password", password); props.put("mail.smtp.port", smtpPort); props.put("mail.smtp.auth", true); props.put("mail.smtp.starttls.enable", "true"); props.put("mail.smtp.debug", "false"); props.put("mail.smtp.socketFactory.port", PORT); props.put("mail.smtp.socketFactory.class", SOCKET_FACTORY); props.put("mail.smtp.socketFactory.fallback", "false"); Session session = Session.getInstance(props, new javax.mail.Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(userAccount, password); } }); MimeMessage mimeMessage = new MimeMessage(session); final Address toAddress = new InternetAddress("****@outlook.com"); // toAddress final Address fromAddress = new InternetAddress(userAccount); mimeMessage.setContent("This is a test mail...", "text/html; charset=UTF-8"); mimeMessage.setFrom(fromAddress); mimeMessage.setRecipient(javax.mail.Message.RecipientType.TO, toAddress); mimeMessage.setSubject("Test Mail..."); Transport transport = session.getTransport("smtp"); transport.connect(smtpServer, userAccount, password); transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients());