I am running a very basic Javamail program to try sending emails. This is stand-alone program with main(). Once I get it working, I plan to use Javamail in a servlet running und
You probably want to use OAuth2 authentication.
I am including my solution as a separate answer. I had previously edited the question to include this, but the question became too long.
Shown below is a servlet that uses OAuth2 to send emails from "Contact" form on my website. I followed the instructions provided in the link provided by Bill's answer.
SendMessage.java (Needs more sophisticated implementation, please read comments within code)
/*
* 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 - Code used unchanged from gmail-oauth2-tools, except a package statement is added at top (package com.somedomain.oauth2;)
OAuth2SaslClientFactory.java - Code used unchanged, package statement added
how can I make my application more secure so that gmail does not refuse authentication?
One good way in my opinion would be to enable two-way authentication and replace the normal Gmail password with the generated application specific password in your code.
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());