Skip to main content

自定义认证

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

何时需要自定义处理器?

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

250-AUTH LOGIN PLAIN MY-CUSTOM-METHOD

在这个响应中,服务器列出了三种可用的认证方法。Nodemailer 已经知道如何处理 LOGINPLAIN,但无法识别 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 扩展数组(如 SIZESTARTTLSPIPELINING)。若你的认证依赖服务器某些功能,此信息非常有用。

ctx.authMethods

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

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挑战-响应认证