NestJS微服务中FCM sendAll()报PEM私钥无起始行错误排查
NestJS调用Firebase FCM sendAll()报PEM格式错误的排查与解决
问题背景
在NestJS微服务后端调用Firebase FCM的sendAll()接口时,触发以下PEM格式错误:
error:0909006C:PEM routines:get_name:no start line
Firebase初始化过程无报错,但调用sendAll()时抛出上述异常,相关代码及错误信息如下:
Firebase初始化代码(main.ts)
const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, { transport: Transport.TCP, options }); const configService: ConfigService = app.get(ConfigService); const adminConfig: ServiceAccount = { "projectId": configService.get<string>('FIREBASE_PROJECT_ID'), "privateKey": configService.get<string>('FIREBASE_PRIVATE_KEY').replace(/\\n/g, '\n'), "clientEmail": configService.get<string>('FIREBASE_CLIENT_EMAIL'), }; firebase.initializeApp({ credential: firebase.credential.cert(adminConfig) }); await app.listen();
.env配置
FIREBASE_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\nMI...\n-----END PRIVATE KEY-----\n" FIREBASE_CLIENT_EMAIL = 'abc@my.com' FIREBASE_PROJECT_ID = 'xxx-genysis'
sendAll()调用代码(notification.service.ts)
import * as firebase from 'firebase-admin'; async sendNotifications(data) { const messages = await this.getMessages(data.tokens,data.message,data.title); const promises = []; for (let i = 0; i + 10 <= messages.length; i += 10) { const batch = messages.slice(i, i + 10); promises.push(firebase.messaging().sendAll(batch)); } await Promise.all(promises); }
错误栈信息
error:0909006C:PEM routines:get_name:no start line Error: error:0909006C:PEM routines:get_name:no start line at Sign.sign (node:internal/crypto/sig:131:29) at Object.sign (/Applications/GenysisBuild/GenysisNest/genysis-notifications/node_modules/jsonwebtoken/node_modules/jwa/index.js:152:45) at Object.jwsSign [as sign] (/Applications/GenysisBuild/GenysisNest/genysis-notifications/node_modules/jsonwebtoken/node_modules/jws/lib/sign-stream.js:32:24) at Object.module.exports [as sign] (/Applications/GenysisBuild/GenysisNest/genysis-notifications/node_modules/jsonwebtoken/sign.js:204:16) at ServiceAccountCredential.createAuthJwt_ (/Applications/GenysisBuild/GenysisNest/genysis-notifications/node_modules/firebase-admin/lib/app/credential-internal.js:105:20) at ServiceAccountCredential.getAccessToken (/Applications/GenysisBuild/GenysisNest/genysis-notifications/node_modules/firebase-admin/lib/app/credential-internal.js:77:28) at FirebaseAppInternals.refreshToken (/Applications/GenysisBuild/GenysisNest/genysis-notifications/node_modules/firebase-admin/lib/app/firebase-app.js:45:49) at FirebaseAppInternals.getToken (/Applications/GenysisBuild/GenysisNest/genysis-notifications/node_modules/firebase-admin/lib/app/firebase-app.js:37:25) at AuthorizedHttpClient.getToken (/Applications/GenysisBuild/GenysisNest/genysis-notifications/node_modules/firebase-admin/lib/utils/api-request.js:612:34) at AuthorizedHttpClient.send (/Applications/GenysisBuild/GenysisNest/genysis-notifications/node_modules/firebase-admin/lib/utils/api-request.js:600:21)
捕获到的错误对象
{"library":"PEM routines","function":"get_name","reason":"no start line","code":"ERR_OSSL_PEM_NO_START_LINE"}
排查思路与解决方法
1. 修复私钥的格式处理逻辑
初始化时Firebase仅做基础校验,但sendAll()请求时会重新使用私钥签名JWT,此时私钥的隐性格式问题会触发错误:
- 检查.env中私钥的外层双引号是否被保留,导致私钥开头多了引号。修改代码,额外去除首尾引号:
"privateKey": configService.get<string>('FIREBASE_PRIVATE_KEY').replace(/\\n/g, '\n').replace(/^"|"$/g, ''), - 打印处理后的
privateKey,确认是否包含正确的-----BEGIN PRIVATE KEY-----开头和-----END PRIVATE KEY-----结尾,且换行符已替换为真实换行(不是字符串\n)。
2. 验证私钥的纯净性
- 确保私钥的每一行(包括首尾标记行)没有多余的空格、制表符或换行,尤其是
-----BEGIN PRIVATE KEY-----和-----END PRIVATE KEY-----不能有任何额外字符。 - 测试直接在代码中写入私钥(仅用于排查),排除.env加载的问题:
const adminConfig: ServiceAccount = { projectId: 'xxx-genysis', privateKey: `-----BEGIN PRIVATE KEY-----
MI...
-----END PRIVATE KEY-----`,
clientEmail: 'abc@my.com',
};
### 3. 避免Firebase App重复初始化 微服务环境中,模块重复导入可能导致多次调用`initializeApp`,造成凭证实例异常。添加初始化判断: ```typescript if (!firebase.apps.length) { firebase.initializeApp({ credential: firebase.credential.cert(adminConfig) }); }
4. 兼容Node.js与SDK版本
部分Node.js版本与OpenSSL的PEM解析逻辑存在兼容性问题:
- 升级Node.js到LTS版本(如18.x或20.x)
- 调整firebase-admin SDK到稳定版本(如11.x或12.x)
5. 改用服务账号文件加载
放弃环境变量加载私钥,直接读取Firebase控制台下载的服务账号JSON文件,避免字符串处理的格式问题:
import * as serviceAccount from './service-account.json'; firebase.initializeApp({ credential: firebase.credential.cert(serviceAccount as ServiceAccount), });
注意将该JSON文件加入.gitignore,避免泄露敏感信息。
内容的提问来源于stack exchange,提问作者MichaelE
相关产品推荐
相关产品推荐

