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

后端 未结 3 807
逝去的感伤
逝去的感伤 2021-02-07 07:33

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

3条回答
  •  我在风中等你
    2021-02-07 08:10

    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 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 - 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

提交回复
热议问题