发布于 2026-01-06 8 阅读
0

使用 Node.js、Nodemailer、SMTP、Gmail 和 OAuth2 安全地发送电子邮件

使用 Node.js、Nodemailer、SMTP、Gmail 和 OAuth2 安全地发送电子邮件

网上很多关于如何配置 Nodemailer 以使用 Gmail 的解决方案都需要您启用安全性较低的应用访问权限。如果您觉得这太过冒险,那么您来对地方了!本文将教您如何安全地配置 Nodemailer 和 Gmail。

我们先来了解一下Nodemailer是什么。

Nodemailer是一个模块,它使从 Node.js 应用程序发送电子邮件变得异常简单。

以下是发送电子邮件的主要步骤:

  1. 使用 SMTP 或其他传输机制创建传输器(用于发送电子邮件的对象)。
  2. 设置消息选项(谁向谁发送什么内容)
  3. 通过调用传输器上的 sendMail 方法发送电子邮件。

安全性较低的配置

在介绍配置 Nodemailer 和 Gmail 的安全解决方案之前,让我们先来看看不太安全的解决方案。

参考以上步骤,以下是相应的代码:



//Step 1: Creating the transporter
const transporter = nodemailer.createTransport({
    service: "Gmail",
    auth: {
          user: "******@gmail.com",
          pass: "gmail_password"
        }
});

//Step 2: Setting up message options
const messageOptions = {
  subject: "Test",
  text: "I am sending an email from nodemailer!",
  to: "put_email_of_the_recipient",
  from: "put_email_of_sender"
};

//Step 3: Sending email
transporter.sendMail(messageOptions);


Enter fullscreen mode Exit fullscreen mode

注意:上述解决方案需要您在 Google 帐户设置中启用安全性较低的应用访问权限才能生效。

现在,我们来看看更安全的解决方案。

步骤 1:创建 Google 项目

请访问Google 开发者控制台创建项目。我们需要创建一个项目,以便创建必要的 API 凭据。

进入控制台后,点击左上角的下拉菜单。

项目下拉菜单

创建项目窗口加载完毕后,单击“新建项目”

项目窗口

输入项目名称,然后点击创建

3

步骤 2:创建 OAuth 2.0 API 凭证

要获取客户端密钥和客户端 ID,我们需要创建 OAuth 凭据。客户端 ID 用于向 Google 的 OAuth 服务器标识我们的应用,以便我们能够通过 Nodemailer 安全地发送电子邮件。

首先,请在左侧边栏中选择“凭据” 。选择后,应出现以下屏幕:

4

点击“创建凭据”后,将出现一个下拉菜单。在下拉菜单中,选择“OAuth 客户端 ID”

5

在继续之前,我们需要配置同意屏幕。当应用程序提供 Google 登录功能时,同意屏幕的配置至关重要。此外,必须完成此配置才能创建客户端 ID 和密钥。

点击配置同意屏幕

6

选择“外部用户”作为用户类型,然后单击“创建”

7

多步骤表单出现后,请填写每个步骤的必填字段。

替代文字

到达最后一步后,点击返回仪表盘

8

返回“创建 OAuth 客户端 ID”屏幕(带有“配置同意屏幕”按钮的页面)。如果同意屏幕配置成功,则会显示一个应用程序类型下拉菜单。选择“Web 应用程序”并填写必填字段。

9

在“已授权重定向 URI”部分,请确保添加https://developers.google.com/oauthplayground

现在点击创建

9.1

复制屏幕上显示的客户端 ID 和客户端密钥并保存以备后用。

9.2

步骤 3:OAuth 2.0 测试平台

我们还需要一个刷新令牌和一个访问令牌,它们可以根据客户端 ID 和密钥生成。

首先访问https://developers.google.com/oauthplayground
进入页面后,点击齿轮图标,勾选“使用您自己的 OAuth 凭据”复选框。然后粘贴之前获取的客户端 ID 和密钥。

9.3

在左侧“选择和授权 API”部分下方,找到 Gmail API v1 并选择https://mail.google.com/。或者,您也可以“输入您自己的范围”字段中输入https://mail.google.com/

现在点击“授权 API”

9.4

如果出现以下页面,请点击允许,以便 Google OAuth 2.0 Playground 可以访问您的 Google 帐户。

9.41

重定向回 OAuth 2.0 Playground 后,
单击“交换授权码以获取令牌”部分下的“交换授权码以获取令牌”按钮

刷新令牌和访问令牌生成后,复制刷新令牌并保存以备后用。

9.5

第四步:编写代码

现在我们有了客户端 ID、客户端密钥和刷新令牌,就可以用它们来发送电子邮件了!

首先为应用程序创建一个新文件夹,然后使用 cd 键进入该文件夹。



mkdir sendEmails
cd sendEmails


Enter fullscreen mode Exit fullscreen mode

要将应用程序初始化为 node 项目,请运行npm init

接下来,我们来安装 npm 包。



//Note: dotenv is a dev dependency
npm i nodemailer googleapis && npm i dotenv --save-dev


Enter fullscreen mode Exit fullscreen mode

谷歌API

  • 用于使用 Google API 的库
  • 将用于动态生成访问令牌

dotenv

  • 用于使用环境变量的库
  • 我们将使用这种方法来避免在代码中使用 API 密钥。

与其他 NPM 包一样,我们首先需要引入这些包。因此,请创建一个index.js文件并添加以下内容:



const nodemailer = require("nodemailer");
const { google } = require("googleapis");
const OAuth2 = google.auth.OAuth2;


Enter fullscreen mode Exit fullscreen mode

环境变量设置

通常情况下,在代码中使用敏感信息(例如 API 密钥)时,最佳实践是使用环境变量。

.env在项目根目录下创建一个文件,并添加以下内容:



EMAIL=YOUR_GOOGLE_EMAIL_HERE
REFRESH_TOKEN=PASTE_REFRESH_TOKEN_HERE
CLIENT_SECRET=PASTE_CLIENT_SECRET_HERE
CLIENT_ID=PASTE_CLIENT_ID_HERE


Enter fullscreen mode Exit fullscreen mode

现在,我们需要在引入所有包之前,先引入并调用配置方法:



require('dotenv').config();
const nodemailer = require("nodemailer");
const { google } = require("googleapis");
const OAuth2 = google.auth.OAuth2;


Enter fullscreen mode Exit fullscreen mode

process.env现在它拥有文件中定义的键和值.env。例如,我们可以通过以下方式访问客户端 ID:process.env.CLIENT_ID

制造传送器

我们首先需要使用之前的所有信息(客户端 ID、客户端密钥和 OAuth Playground URL)创建一个 OAuth 客户端。该 OAuth 客户端将允许我们从刷新令牌动态创建访问令牌。

“等等,为什么我们不能直接使用 OAuth Playground 中的访问令牌呢?或者,为什么要动态创建访问令牌呢?”

嗯,如果你之前注意到的话,会有一条消息表明访问令牌将在 3582 秒后过期。

以下代码创建 OAuth 客户端并向其提供刷新令牌:



const oauth2Client = new OAuth2(
    process.env.CLIENT_ID,
    process.env.CLIENT_SECRET,
    "https://developers.google.com/oauthplayground"
);

oauth2Client.setCredentials({
    refresh_token: process.env.REFRESH_TOKEN
});


Enter fullscreen mode Exit fullscreen mode

由于通过 OAuth 客户端获取访问令牌是一个异步过程,我们需要将上述操作包装在一个异步函数中。



const createTransporter = async () => {
  const oauth2Client = new OAuth2(
    process.env.CLIENT_ID,
    process.env.CLIENT_SECRET,
    "https://developers.google.com/oauthplayground"
  );

  oauth2Client.setCredentials({
    refresh_token: process.env.REFRESH_TOKEN
  });
};


Enter fullscreen mode Exit fullscreen mode

现在,我们可以通过调用 getAccessToken 方法来获取访问令牌。



const accessToken = await new Promise((resolve, reject) => {
  oauth2Client.getAccessToken((err, token) => {
    if (err) {
      reject("Failed to create access token :(");
    }
    resolve(token);
  });
});


Enter fullscreen mode Exit fullscreen mode

你可能想知道,为什么要把 `getAccessToken` 方法调用包装在一个 Promise 中?这是因为 `getAccessToken` 需要一个回调函数,并且不支持使用 `async/await`。因此,我们可以将其包装在一个 Promise 中,或者在回调函数内部创建传输器。我更喜欢前者,因为它更易读。

现在进入主要部分,创建运输器对象本身。要创建它,我们需要将一些配置传递给 createTransport 方法。



const transporter = nodemailer.createTransport({
  service: "gmail",
  auth: {
    type: "OAuth2",
    user: process.env.EMAIL,
    accessToken,
    clientId: process.env.CLIENT_ID,
    clientSecret: process.env.CLIENT_SECRET,
    refreshToken: process.env.REFRESH_TOKEN
  }
});


Enter fullscreen mode Exit fullscreen mode

注意:如果您收到“未授权客户端”错误,请尝试将以下内容添加到上面的 JS 对象中。



tls: {
  rejectUnauthorized: false
}


Enter fullscreen mode Exit fullscreen mode

运输器创建完成后,完整的 createTransporter 函数应如下所示:



const createTransporter = async () => {
  const oauth2Client = new OAuth2(
    process.env.CLIENT_ID,
    process.env.CLIENT_SECRET,
    "https://developers.google.com/oauthplayground"
  );

  oauth2Client.setCredentials({
    refresh_token: process.env.REFRESH_TOKEN
  });

  const accessToken = await new Promise((resolve, reject) => {
    oauth2Client.getAccessToken((err, token) => {
      if (err) {
        reject();
      }
      resolve(token);
    });
  });

  const transporter = nodemailer.createTransport({
    service: "gmail",
    auth: {
      type: "OAuth2",
      user: process.env.EMAIL,
      accessToken,
      clientId: process.env.CLIENT_ID,
      clientSecret: process.env.CLIENT_SECRET,
      refreshToken: process.env.REFRESH_TOKEN
    }
  });

  return transporter;
};


Enter fullscreen mode Exit fullscreen mode

请注意,我们返回的是传输器,而不是编写发送电子邮件的代码。为了提高代码可读性和分离关注点,我们将创建另一个函数来发送电子邮件。

现在我们来创建 sendEmail 函数。该函数会调用 createTransporter 函数,然后再调用传输器上存在的 sendMail 方法。



//emailOptions - who sends what to whom
const sendEmail = async (emailOptions) => {
  let emailTransporter = await createTransporter();
  await emailTransporter.sendMail(emailOptions);
};


Enter fullscreen mode Exit fullscreen mode

现在剩下的就是调用 sendEmail 函数发送电子邮件了:



sendEmail({
  subject: "Test",
  text: "I am sending an email from nodemailer!",
  to: "put_email_of_the_recipient",
  from: process.env.EMAIL
});


Enter fullscreen mode Exit fullscreen mode

完整的电子邮件选项列表可在https://nodemailer.com/message/找到。

node index.js从终端/命令行运行,瞧!这就是我们从应用程序发送的电子邮件!

替代文字

供您参考,以下是已完成的index.js文件:



require("dotenv").config();
const nodemailer = require("nodemailer");
const { google } = require("googleapis");
const OAuth2 = google.auth.OAuth2;

const createTransporter = async () => {
  const oauth2Client = new OAuth2(
    process.env.CLIENT_ID,
    process.env.CLIENT_SECRET,
    "https://developers.google.com/oauthplayground"
  );

  oauth2Client.setCredentials({
    refresh_token: process.env.REFRESH_TOKEN
  });

  const accessToken = await new Promise((resolve, reject) => {
    oauth2Client.getAccessToken((err, token) => {
      if (err) {
        reject("Failed to create access token :(");
      }
      resolve(token);
    });
  });

  const transporter = nodemailer.createTransport({
    service: "gmail",
    auth: {
      type: "OAuth2",
      user: process.env.EMAIL,
      accessToken,
      clientId: process.env.CLIENT_ID,
      clientSecret: process.env.CLIENT_SECRET,
      refreshToken: process.env.REFRESH_TOKEN
    }
  });

  return transporter;
};

const sendEmail = async (emailOptions) => {
  let emailTransporter = await createTransporter();
  await emailTransporter.sendMail(emailOptions);
};

sendEmail({
  subject: "Test",
  text: "I am sending an email from nodemailer!",
  to: "put_email_of_the_recipient",
  from: process.env.EMAIL
});


Enter fullscreen mode Exit fullscreen mode
文章来源:https://dev.to/chandrapantachhetri/sending-emails-securely-using-node-js-nodemailer-smtp-gmail-and-oauth2-g3a