使用 javamail,gmail 由于应用程序安全性较低而拒绝身份验证
Using javamail, gmail refusing authentication due to application being less secure
我是 运行 一个非常基本的 Javamail 程序,用于尝试发送电子邮件。这是带有 main() 的独立程序。一旦我开始工作,我计划在 tomcat.
下的 servlet 运行 中使用 Javamail
当 运行 这个程序时,我收到 AUTH LOGIN failed 错误。我尝试了几种不同的 属性 设置,其中 none 解决了问题。
然后我在 SO 上发现了一个 post,它建议降低我的 Google 帐户中所需的安全级别。当我降低安全设置时,身份验证成功。
当然,我立即返回 Google 帐户的更高安全级别。
我的问题是,如何使我的应用程序更安全,以便 gmail 不拒绝身份验证?
程序代码如下所示。该程序与 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();
}
}
how can I make my application more secure so that gmail does not
refuse authentication?
我认为一个好方法是在代码中启用 two-way authentication and replace the normal Gmail password with the generated application specific password .
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());
您可能想使用 OAuth2 authentication。
我将我的解决方案作为单独的答案包含在内。我之前编辑过问题以包含此问题,但问题变得太长了。
下面使用 OAuth2 身份验证的 Servlet
下面显示的是一个使用 OAuth2 从我网站上的 "Contact" 表单发送电子邮件的 servlet。我按照比尔的回答提供的 link 中的说明进行操作。
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<String,Object> 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<String,Object> 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 相同,只是在顶部添加了一个包语句(包 com.somedomain.oauth2;)
OAuth2SaslClientFactory.java - 使用的代码未更改,添加了包语句
我是 运行 一个非常基本的 Javamail 程序,用于尝试发送电子邮件。这是带有 main() 的独立程序。一旦我开始工作,我计划在 tomcat.
下的 servlet 运行 中使用 Javamail当 运行 这个程序时,我收到 AUTH LOGIN failed 错误。我尝试了几种不同的 属性 设置,其中 none 解决了问题。
然后我在 SO 上发现了一个 post,它建议降低我的 Google 帐户中所需的安全级别。当我降低安全设置时,身份验证成功。
当然,我立即返回 Google 帐户的更高安全级别。
我的问题是,如何使我的应用程序更安全,以便 gmail 不拒绝身份验证?
程序代码如下所示。该程序与 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();
}
}
how can I make my application more secure so that gmail does not refuse authentication?
我认为一个好方法是在代码中启用 two-way authentication and replace the normal Gmail password with the generated application specific password .
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());
您可能想使用 OAuth2 authentication。
我将我的解决方案作为单独的答案包含在内。我之前编辑过问题以包含此问题,但问题变得太长了。
下面使用 OAuth2 身份验证的 Servlet
下面显示的是一个使用 OAuth2 从我网站上的 "Contact" 表单发送电子邮件的 servlet。我按照比尔的回答提供的 link 中的说明进行操作。
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<String,Object> 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<String,Object> 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 相同,只是在顶部添加了一个包语句(包 com.somedomain.oauth2;)
OAuth2SaslClientFactory.java - 使用的代码未更改,添加了包语句