Google Cloud Function V2升级后OAuth2认证SMTP登录失败
将使用4年的Cloud Function从Gen 1升级到Gen 2,改用defineSecret替代functions.config()获取环境变量,并用OAuth2认证替代Gmail的“不安全应用”授权。函数部署成功,但运行时出现invalid user or password错误,该函数用于将联系表单数据通过Gmail发送给自己。
已完成配置:
- OAuth consent screen设为测试模式,自身邮箱已添加为测试用户
- 通过OAuth 2.0 Playground生成了带有
https://www.googleapis.com/auth/gmail.send权限的refresh token
日志显示“Access Token obtained successfully”,但随后报错:
Error creating transport: Error: Invalid login: 535-5.7.8 Username and Password not accepted.
相关函数代码:
const clientId = defineSecret("CLIENT_ID"); const clientSecret = defineSecret("CLIENT_SECRET"); const refreshToken = defineSecret("REFRESH_TOKEN"); async function getAccessToken(oauth2Client) { try { const { token } = await oauth2Client.getAccessToken(); return token; } catch (error) { console.error('Error getting access token:', error); throw new Error(`Failed to get access token: ${error.message}`); } } const createTransport = async () => { try { const oauth2Client = new google.auth.OAuth2( await clientId.value(), await clientSecret.value(), "https://developers.google.com/oauthplayground" ); oauth2Client.setCredentials({ refresh_token: await refreshToken.value() }); const accessToken = await getAccessToken(oauth2Client); if (!accessToken) { throw new Error('Failed to obtain access token'); } console.log('Access Token obtained successfully'); const transportConfig = { host: 'smtp.gmail.com', port: 465, secure: true, auth: { type: 'OAuth2', user: await emailSender.value(), clientId: await clientId.value(), clientSecret: await clientSecret.value(), refreshToken: await refreshToken.value(), accessToken, }, }; const transporter = nodemailer.createTransport(transportConfig); await transporter.verify(); console.log('SMTP connection verified successfully'); return transporter; } catch (error) { console.error('Error creating transport:', error); throw new Error(`Failed to create transport: ${error.message}`); } };
核对发送邮箱一致性
user: await emailSender.value()对应的邮箱必须和生成refresh token时使用的测试用户邮箱完全一致,且该邮箱已在OAuth consent screen的测试用户列表中。校验OAuth Playground配置
- 必须使用自己Google Cloud项目的Client ID和Client Secret,而非Playground默认的测试ID
- 授权时需明确勾选
https://www.googleapis.com/auth/gmail.send权限,测试模式下refresh token有效期为7天,若过期需重新生成
简化Nodemailer OAuth2配置
Nodemailer会自动用refresh token刷新access token,无需手动获取。移除手动传入的accessToken,避免过期或格式问题:const transportConfig = { host: 'smtp.gmail.com', port: 465, secure: true, auth: { type: 'OAuth2', user: await emailSender.value(), clientId: await clientId.value(), clientSecret: await clientSecret.value(), refreshToken: await refreshToken.value() }, };验证Secret Manager配置
确认Cloud Function的服务账号拥有访问Secret Manager的权限,且CLIENT_ID、CLIENT_SECRET、REFRESH_TOKEN、emailSender的Secret值已正确存入,取值逻辑无错误。检查Gmail账户状态
确保目标Gmail账户未开启两步验证(OAuth2模式下建议关闭,若开启需使用应用专用密码),同时确认账户未被Google限制邮件发送权限。
内容的提问来源于stack exchange,提问作者Ange Loron

