【问题标题】:NodeMailer - send mail with Google service account fails because "Username and Password not accepted"NodeMailer - 使用 Google 服务帐户发送邮件失败,因为“用户名和密码不被接受”
【发布时间】:2017-12-20 16:43:24
【问题描述】:

我正在创建一个 Twitter 机器人,并且我正在实现一个在出现错误时向我发送电子邮件的方法。由于我已经在使用 google API 访问 Google Drive(这里没有问题),我决定使用服务帐户发送电子邮件(Google 控制台说可以这样使用)

到目前为止,我想出的发送电子邮件的方法是:

var config = require('./config/mail');
var google = require('./config/google');
var nodemailer = require('nodemailer');

var send = function (args) {
  let transporter = nodemailer.createTransport({
    'service': 'gmail',
    'auth': {
        'type': 'OAuth2',
        'user': google.client_email,
        'serviceClient': google.client_id,
        'privateKey': google.private_key
    }
  });
  transporter.on('token', token => console.log(token));

  let message = {
    'from': `"${config.serverFromName}" <${config.serverFromMail}>`,
    'to': args.to,
    'subject': args.subject,
    'text': args.text,
    'html': `<p>${args.text}</p>`
  };

  transporter.sendMail(message, (err, info) => {
    if (err) {
      console.log('Mail couldn\'t be sent because: ' + err);
    } else {
      console.log('Mail sent');
    }
  });
};

config/google 文件包含 Google 在您创建服务帐户时为您生成的数据。 config.serverFromNameconfig.serverFromMail 是发件人的姓名和电子邮件(与服务帐户 ID 不同)。 args 包含收件人电子邮件和内容

当我测试发送方法时,我在控制台中收到以下消息:

Mail couldn't be sent because: Error: Invalid login: 535-5.7.8 Username and Password not accepted. Learn more at
535 5.7.8  https://support.google.com/mail/?p=BadCredentials z123sm543690vkd.10 - gsmtp

我知道令牌正在正确创建,因为我创建的侦听器正在打印它:

{ user: 'name@project.iam.gserviceaccount.com',
  accessToken: 'ya29.ElmIBLxzfU_kkuZeyISeuRBeljmAe7HNTlwuG4K12ysUNo46s-eJ8NkMYHQqD_JrqTlH3yheNc2Aopu9B5vw-ivEqvPR4sTDpWBOg3xUU_4XiJEBLno8FHsg',
  expires: 1500151434603 }

上网搜索发现可能是OAuth范围的问题。但是,所有关于它的信息都是指使用客户端 ID,而不是服务帐户。我也没有在 Google 开发者控制台中找到该选项。

对我做错了什么有任何想法吗?

【问题讨论】:

  • 希望您已经轮换了该访问令牌...
  • @Amber 我认为如果没有人知道用户电子邮件,令牌将一文不值。我错了吗?
  • 电子邮件比令牌更容易被猜测,因此发布令牌会降低有效安全性。我仍然建议旋转它。
  • 对此有何更新?有同样的问题
  • 我是用nodemailer.com/smtp/oauth2/#oauth-2lo 之后的服务帐户完成的 - 范围很痛苦,我的是 'https://mail.google.com/', 'https://www.googleapis.com/auth/gmail.compose', 'https://www.googleapis.com/auth/gmail.modify', 'https://www.googleapis.com/auth/gmail.send '

标签: node.js email google-oauth google-apps nodemailer


【解决方案1】:

底线:Google 描述服务帐户的具体方式与 nodemailer 不兼容。但是有办法!

我自己刚刚在同样的问题上花费了无数个小时!我得出的结论是,Google 的 Admin Console 已间接删除了一半的此功能。控制台没有提供一种方法来在第一次使用服务帐户时授权(用户接受同意屏幕)所需的范围。

首先,按照 Google Drive API 的 Node.JS Quickstart 说明授权范围并接收刷新令牌。

  1. 转到console.developers.google.com,构建 OAuth2.0 客户端 ID,并下载 client_secret.json 文件。

  2. 创建一个单独的临时模块文件夹并使用NPM下载google api模块

    npm 安装 googleapis

    npm install google-auth-library

  3. 创建一个 quickstart.js 文件

  4. 将您的 client_secret.json 文件放在 quickstart.js 旁边

  5. quickstart.js 中的第 7 行是定义您打算允许应用程序访问的范围的数组。根据需要对其进行修改。强烈建议只为预期的内容提供访问权限。见Gmail API Scopes

  6. 运行node quickstart.js

  7. 在浏览器中打开 URL,进行身份验证,然后将代码从浏览器复制回终端窗口。这将下载一个 nodejs-gmail-quickstart.json 文件,该文件的位置将在 stdout 中提供。
    这是您无法为服务帐户完成的部分。此操作将 SCOPES 数组中提供的范围授权给下载的 access_token 和刷新令牌。

注意:access_token 的有效期为 1 小时。 refresh_token 是不朽的。

现在你有一个授权的 refresh_token! 接下来是在Nodemailer 中使用 3LO 设置您的身份验证对象。我会更多地查看底部示例,因为并非所有值都是必需的。我的身份验证如下所示:

const mailbot = nodemailer.createTransport({
      host: 'smtp.gmail.com',
      port: 587,              // TLS (google requires this port for TLS)
      secure: false,          // Not SSL
      requireTLS: true,       // Uses STARTTLS command (nodemailer-ism)
      auth: {
          // **HIGHLY RECOMMEND** ALL values be
          //  read in from a file not placed directly in code.  
          // Make sure that file is locked down to only the server daemon
          type : 'OAuth2',
          user : config.client_email,
          scope : "https://www.googleapis.com/auth/gmail.send",
          clientId : config.client_id,
          clientSecret: secret,
          refreshToken: activeToken.refresh_token

          // AT RUNTIME, it looks like this:
          //type : 'OAuth2',
          //user : 'user@gmail.com',   // actual user being impersonated
          //scope : "", //Optional, but recommend to define for the action intended
          //clientId : '888888888998-9xx9x99xx9x99xx9xxxx9xx9xx9x88x8xxx.apps.googleusercontent.com',
          //clientSecret: 'XxxxxXXxX0xxxxxxxx0XXxX0',
          //refreshToken: '1/XXxXxsss-xxxXXXXXxXxx0XXXxxXXx0x00xxx'              
      }
 });

提示:Gmail 将重写使用授权用户帐户(模拟用户)发送的任何电子邮件的 FROM 字段。如果您想稍微自定义一下,请使用语法{ FROM: '"Display NAME" &lt;user email&gt;' },它不会覆盖您选择的显示名称,因为电子邮件匹配。

注意:nodemailer 将使用刷新令牌向https://accounts.google.com/o/oauth2/token 发出令牌请求,以自动获取 access_token。

不幸的是,nodemailer 缺少将接收到的令牌直接保存到文件中的功能,而只是使用 this.emit()。如果服务器保持活动状态,这不会是问题,但由于我的只是爆发,它总是会产生延迟,因为每次都会请求新的 access_token。

[安全] 希望这对您有用!使用 2LO 的服务帐户会带来的私钥加密令人失望,但至少这种客户端 ID 方式很难被欺骗。我担心安全性,但阅读更多我对这个实现没问题。请参阅Google Identity Platform(Nodemailer 使用 HTTP/REST 详细信息)并给出

[1] Google 的 OAuth 2.0 端点位于 https://accounts.google.com/o/oauth2/v2/auth。这个端点是 只能通过 HTTPS 访问。普通 HTTP 连接被拒绝。

[5]web服务器收到授权码后,可以交换 访问令牌的授权码。

您最初使用 TLS 连接以获取授权代码,然后将其与您的客户端 ID 数据和 refresh_token 匹配(您必须经历我们上面所做的麻烦),然后您可以接收 access_token 以实际与 Google API 交互.

只要您通过保持 OAuth2.0 客户端 ID(高度随机的用户名)、机密和刷新令牌尽可能独立、安全和隐藏来提高安全性,您应该能够睡个好觉。 祝你好运!

【讨论】:

  • 您可以直接使用 GMAIL 配置,不要忘记通过链接 myaccount.google.com/lesssecureapps?pli=1 启用不太安全的应用程序
  • 感谢您提醒不太安全的应用启用。如果您不同意并让它直接工作,请发布答案@HemantRajpoot。
  • 对不起,但目前它可以使用纯服务帐户,没有黑客的方式来做到这一点。 nodemailer.com/smtp/oauth2/#oauth-2lo我刚试了一下,效果很好
【解决方案2】:

在访问了OAuth 2.0 Playground 并尝试了gmail-related sub-scopes 的所有可能变体之后,甚至完全选择了它们......

https://www.googleapis.com/auth/gmail.labels
https://www.googleapis.com/auth/gmail.send
https://www.googleapis.com/auth/gmail.readonly
https://www.googleapis.com/auth/gmail.compose
https://www.googleapis.com/auth/gmail.insert
https://www.googleapis.com/auth/gmail.modify
https://www.googleapis.com/auth/gmail.metadata
https://www.googleapis.com/auth/gmail.settings.basic
https://www.googleapis.com/auth/gmail.settings.sharing

...OP标题中描述的错误信息仍然存在:

Error: Invalid login: 535-5.7.8 Username and Password not accepted

NodeMailer 似乎无法通过上述范围进行连接。事实上,它在其OAuth2 SMTP transport docs

的“疑难解答”部分中明确提及

Gmail SMTP 的正确 OAuth2 范围是 https://mail.google.com/,请确保您的客户端在为用户请求权限时设置了此范围

虽然这不仅可以发送电子邮件,还可以访问更多内容!

达到更细粒度范围解决方案的唯一替代方法似乎是求助于谷歌自己的Gmail API,您可以在生成 OAuth2 客户端时传递范围(当然至少应该包括当时授予的范围显示 OAuth 同意屏幕):

oAuth2Client.generateAuthUrl({
  access_type: 'offline',
  scope: SCOPES,
})

【讨论】:

    【解决方案3】:

    我能够获得使用 Google 和 nodemailer 的服务帐户:

    这些是步骤:

    1. 登录控制台。- https://console.cloud.google.com/
    2. 在项目下创建服务帐号。
    3. 点击新的服务账户,进入权限并添加成员。发送请求时,您将使用该成员的电子邮件地址。
    4. 为服务帐户创建密钥。 - 键 -> 添加键。 https://console.cloud.google.com/iam-admin/serviceaccounts
    5. 下载您的密钥文件。你会得到类似 service-account-name-accountid.json 的东西。它将包含运行以下代码所需的所有信息。
    6. 将权限委派给您的服务帐户https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority。添加https://mail.google.com/ 作为范围。
    7. 编写如下代码:
    
    const nodemailer = require('nodemailer');
    const json = require('./service-account-name-accountid.json');
    
    const sendEmail = async (email, subject, text) => {
        try {
    
            const transporter = nodemailer.createTransport({
                host: 'smtp.gmail.com',
                port: 465,
                secure: true,
                auth: {
                    type: 'OAuth2',
                    user: email, //your permissioned service account member e-mail address
                    serviceClient: json.client_id,
                    privateKey: json.private_key
                }
            });
    
            await transporter.verify();
            
            await transporter.sendMail({
                    from: json.service_email,
                    to: email, //you can change this to any other e-mail address and it should work!
                    subject,
                    text
            });
            console.log('success!');
            return {
                status : 200
            }
    
        } catch (error) {
            console.log(error);
            return {
                status : 500,
                error
            }
        }
    }
    
    sendEmail('your_permissioned_service_account_email_address@some_place.com, 'testing 123', 'woohoo!');
    

    【讨论】:

      猜你喜欢
      • 2019-06-10
      • 1970-01-01
      • 2022-08-12
      • 2018-02-07
      • 2018-03-03
      • 1970-01-01
      • 2015-11-13
      • 2020-11-08
      • 2021-08-05
      相关资源
      最近更新 更多