跳到主要内容

自定义认证

Nodemailer 的 SMTP 传输 默认支持常见的认证机制,如 LOGIN、PLAIN 和 XOAUTH2。但是,一些 SMTP 服务器使用 Nodemailer 无法识别的专有或不常见的认证方法。针对这些情况,你可以创建自定义认证处理器。

何时需要自定义处理器?​

连接到 SMTP 服务器时,服务器会声明它支持哪些认证方法。例如,服务器可能返回:

250-AUTH LOGIN PLAIN MY-CUSTOM-METHOD

在这个响应中,服务器列出了三种可用的认证方法。Nodemailer 已经知道如何处理 LOGIN 和 PLAIN,但无法识别 MY-CUSTOM-METHOD。如果没有自定义处理器,Nodemailer 无法使用此方法进行认证。

通过提供一个与方法名称完全匹配的处理器,你可让 Nodemailer 完成认证过程。

如果服务器支持多种认证方法,Nodemailer 会自动选择其中一种。若想覆盖此行为并强制 Nodemailer 使用你的自定义方法,请将 auth.method 设置为与你的处理器名称相同。


定义处理器​

要创建自定义认证处理器,请在你的 transporter 选项中添加一个 customAuth 对象。该对象的每个键是认证方法名称(不区分大小写,但通常使用大写),对应的值是一个执行认证流程的函数。

const nodemailer = require("nodemailer");

// 定义自定义认证处理器
async function myCustomMethod(ctx) {
// 构造并发送包含自定义数据的 AUTH 命令
// 此示例发送 base64 编码的密码(根据你的服务器要求调整)
const response = await ctx.sendCommand(
"AUTH MY-CUSTOM-METHOD " + Buffer.from(ctx.auth.credentials.pass).toString("base64")
);

// 检查服务器是否接受了认证
// SMTP 成功状态码为 2xx(通常 235 表示认证成功)
if (response.status < 200 || response.status >= 300) {
throw new Error("认证失败: " + response.text);
}
}

const transporter = nodemailer.createTransport({
host: "smtp.example.com",
port: 465,
secure: true,
auth: {
type: "custom", // 告诉 Nodemailer 使用自定义处理器
method: "MY-CUSTOM-METHOD", // 指定使用哪个处理器
user: "username",
pass: "verysecret",
},
customAuth: {
"MY-CUSTOM-METHOD": myCustomMethod,
},
});

处理器函数签名​

(ctx: HandlerContext) => Promise<void> | void

你的处理器函数接收一个上下文对象 (ctx),并可以通过两种方式表示完成状态:

  1. 使用 Promise(推荐):返回一个 Promise,成功时 resolve,失败时 reject。你也可以用 async 函数,其本身返回 Promise。

  2. 使用回调方式:调用 ctx.resolve() 表示成功,调用 ctx.reject(err) 表示失败。适用于回调驱动的代码场景。

上下文对象属性​

上下文对象 (ctx) 提供了完成认证所需的所有信息:

ctx.auth​

你传入 createTransport() 的完整 auth 对象,包括你添加的自定义属性。

ctx.auth.credentials​

身份验证凭据的便捷别名:

属性描述
user来自 auth.user 的用户名
pass来自 auth.pass 的密码
options来自 auth.options 的其他附加选项

ctx.method​

使用的认证方法名(与 auth.method 值相同)。

ctx.extensions​

服务器支持的 SMTP 扩展数组(如 SIZE、STARTTLS、PIPELINING)。若你的认证依赖服务器某些功能,此信息非常有用。

ctx.authMethods​

服务器声明支持的认证方法数组(如 LOGIN、PLAIN、XOAUTH2)。你可以检查期望的方法是否可用。

ctx.maxAllowedSize​

服务器接受的最大消息大小(字节为单位),若服务器未声明限制,则为 false。

ctx.sendCommand(command)​

向服务器发送原始 SMTP 命令,返回一个 Promise,resolve 时带服务端响应。这是你实现认证协议的主要手段。

响应对象属性:

属性示例描述
status235SMTP 状态码(数字类型)
code2.7.0增强状态码(如果有)
textAuthentication successful服务器的人类可读消息
response235 2.7.0 Authentication successful服务器完整响应行
commandAUTH MY-CUSTOM-METHOD ...发送的命令

回调风格:

若你偏好回调,可以为 sendCommand 提供一个可选回调:

ctx.sendCommand(command, (err, response) => {
if (err) {
return ctx.reject(err);
}
// 处理响应...
ctx.resolve();
});

ctx.resolve() 和 ctx.reject(err)​

不使用 Promise 时,用这两个方法标示认证结果:

  • ctx.resolve():认证成功时调用。
  • ctx.reject(err):认证失败时调用,参数为 Error 对象或错误消息。

如果你使用 async 函数或 Promise,一般不需要直接调用它们。


传递额外参数​

如果认证方法需要不仅仅是用户名和密码,还可在 auth 配置中包含一个 options 对象。这些额外参数可通过 ctx.auth.credentials.options 访问。

const transporter = nodemailer.createTransport({
host: "smtp.example.com",
port: 465,
secure: true,
auth: {
type: "custom",
method: "MY-CUSTOM-METHOD",
user: "username",
pass: "verysecret",
options: {
clientId: "my-client-id",
applicationId: "my-app",
},
},
customAuth: {
"MY-CUSTOM-METHOD": async (ctx) => {
// 通过 ctx.auth.credentials.options 访问额外参数
const { clientId, applicationId } = ctx.auth.credentials.options;

// 使用自定义逻辑生成令牌
const token = await generateSecretToken(clientId, applicationId);

// 发送认证命令
const response = await ctx.sendCommand("AUTH MY-CUSTOM-METHOD " + token);

if (response.status < 200 || response.status >= 300) {
throw new Error("认证失败: " + response.text);
}
},
},
});

社区提供的处理器​

以下包提供了针对特定认证方法的现成处理器:

机制包名备注
NTLMnodemailer-ntlm-authWindows 集成认证 (NTLM)
CRAM-MD5nodemailer-cram-md5挑战-响应认证