AWS Lambda NodeJS:Parameter Store取值后全局变量赋值异步问题
解决Node.js Lambda从AWS Parameter Store获取密码的异步问题
问题场景
开发NodeJS Lambda应用时,需要从AWS Parameter Store获取加密字符串作为基础访问认证的密码,但受Node.js异步特性影响,后续代码无法获取到正确的密码值:
- 全局变量
authPass初始值为default - 调用异步方法
getParameterFromSystemManager后,立即执行后续代码时,authPass仍为初始值 - 异步回调触发时才会更新
authPass为正确值password2,但此时认证逻辑已经执行完毕
原代码
'use strict'; const AWS = require('aws-sdk'); const ssm = new AWS.SSM({region: 'us-east-1'}); var authPass = 'default'; let authUser = 'user'; exports.handler = (event, context, callback) => { // Get request and request headers const request = event.Records[0].cf.request; const headers = request.headers; getParameterFromSystemManager( (data) => { authPass = data; console.log('Inside func authPass : ', authPass); // 此处输出正确值password2 } ); console.log("Outside function call : ", authPass); // 此处输出初始值default // Construct the Basic Auth string const authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64'); // Require Basic authentication if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) { const body = 'Unauthorized'; const response = { status: '401', statusDescription: 'Unauthorized', body: body, headers: { 'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}] }, }; callback(null, response); } callback(null, request); }; function getParameterFromSystemManager(callback) { var params = { Name: '/Path/ToPassword/EncryptedString', WithDecryption: true }; ssm.getParameter(params, function(err, data) { if (err) { console.log(err, err.stack); // an error occurred } else { callback(data.Parameter.Value); } }); }
运行日志
Function Logs START RequestId: 7d2eaccf-f064-4a5b-94c2-f8ff666dxxxx Version: $LATEST 2023-05-18T00:21:13.937Z 7d2eaccf-f064-4a5b-94c2-f8ff666dxxxx INFO Outside function call : default 2023-05-18T00:21:14.177Z 7d2eaccf-f064-4a5b-94c2-f8ff666dxxxx INFO Inside func : data password2 authPass password2 END RequestId: 7d2eaccf-f064-4a5b-94c2-f8ff666dxxx REPORT RequestId: 7d2eaccf-f064-4a5b-94c2-f8ff666dxxxx Duration: 676.17 ms Billed Duration: 677 ms Memory Size: 128 MB Max Memory Used: 81 MB Init Duration: 503.74 ms
问题原因
Node.js的异步非阻塞特性导致:getParameterFromSystemManager调用后,JavaScript会继续执行后续的console.log、认证逻辑等代码,而不会等待异步操作完成。只有当AWS SSM的API请求返回结果后,才会触发回调函数更新authPass,但此时认证逻辑已经执行完毕,使用的还是初始的default值。
解决方案
方案1:将认证逻辑移到异步回调内
把所有依赖authPass的代码放到回调函数中,确保只有获取到正确密码后才执行认证逻辑:
'use strict'; const AWS = require('aws-sdk'); const ssm = new AWS.SSM({region: 'us-east-1'}); let authUser = 'user'; exports.handler = (event, context, callback) => { const request = event.Records[0].cf.request; const headers = request.headers; getParameterFromSystemManager( (authPass) => { console.log('Inside func authPass : ', authPass); // 构造认证字符串 const authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64'); // 执行认证逻辑 if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) { const body = 'Unauthorized'; const response = { status: '401', statusDescription: 'Unauthorized', body: body, headers: { 'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}] }, }; callback(null, response); return; } callback(null, request); }, (err) => { // 处理获取参数失败的情况 console.error('Failed to get parameter:', err); const response = { status: '500', statusDescription: 'Internal Server Error', body: 'Server Error', }; callback(null, response); } ); }; function getParameterFromSystemManager(successCallback, errorCallback) { var params = { Name: '/Path/ToPassword/EncryptedString', WithDecryption: true }; ssm.getParameter(params, function(err, data) { if (err) { errorCallback(err); } else { successCallback(data.Parameter.Value); } }); }
方案2:使用Promise + Async/Await(推荐)
将异步方法封装为Promise,然后在Lambda handler中使用async/await语法,让代码逻辑更线性、易读:
'use strict'; const AWS = require('aws-sdk'); const ssm = new AWS.SSM({region: 'us-east-1'}); const authUser = 'user'; // 将getParameter封装为Promise async function getParameterFromSystemManager() { const params = { Name: '/Path/ToPassword/EncryptedString', WithDecryption: true }; try { const data = await ssm.getParameter(params).promise(); return data.Parameter.Value; } catch (err) { console.error('Failed to get parameter:', err); throw err; } } // 使用async handler exports.handler = async (event, context) => { const request = event.Records[0].cf.request; const headers = request.headers; try { // 等待获取密码完成 const authPass = await getParameterFromSystemManager(); console.log('Got authPass:', authPass); // 构造认证字符串 const authString = 'Basic ' + Buffer.from(`${authUser}:${authPass}`).toString('base64'); // 执行认证逻辑 if (!headers.authorization || headers.authorization[0].value !== authString) { return { status: '401', statusDescription: 'Unauthorized', body: 'Unauthorized', headers: { 'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}] }, }; } return request; } catch (err) { return { status: '500', statusDescription: 'Internal Server Error', body: 'Server Error', }; } };
说明
- 方案2使用了AWS SDK内置的
.promise()方法将回调式API转换为Promise,配合async/await让异步代码看起来像同步代码,可读性更强 - 两种方案都确保了认证逻辑在获取到正确密码后才执行,避免了异步时序问题
- 新增了错误处理逻辑,当获取参数失败时返回500错误,提升了代码健壮性
内容的提问来源于stack exchange,提问作者Milind Rajpoot
相关产品推荐
相关产品推荐

