Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using javamail, gmail refusing authentication due to application being less secure

Tags:

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 under tomcat.

I was getting AUTH LOGIN failed error when running this program. I tried several different property settings, none of which solved the problem.

Then I found a post on SO, which suggested lowering the required security level in my Google account. The authentication was successful when I lowered the security setting.

Of course, I went back to higher security level on the Google account immediately.

The question I have is, how can I make my application more secure so that gmail does not refuse authentication?

Program code shown below. The program is very similar to code in many other Javamail questions on SO.

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 = "[email protected]";
    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();
  }
}
like image 799
Kalyan Vedala Avatar asked Sep 26 '15 19:09

Kalyan Vedala


People also ask

What is JavaMail used for?

The JavaMail API provides a platform-independent and protocol-independent framework to build mail and messaging applications. The JavaMail API is available as an optional package for use with the Java SE platform and is also included in the Java EE platform.

How do I add email to Java?

Browse to the location of your library and Add it. Expand the javamail library. Click Source attachment and click the Edit button. Browse to the location of your source zip file and add it.


2 Answers

You probably want to use OAuth2 authentication.

like image 85
Bill Shannon Avatar answered Sep 17 '22 00:09

Bill Shannon


I am including my solution as a separate answer. I had previously edited the question to include this, but the question became too long.

Servlet using OAuth2 authentication below

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

like image 37
Kalyan Vedala Avatar answered Sep 18 '22 00:09

Kalyan Vedala