不使用AWS SDK,AWS Lambda向S3传文件遇AccessDenied错误
解决原生Node.js上传S3的AccessDenied问题
你的核心问题是原生HTTP请求缺少AWS签名认证,AWS SDK会自动处理请求签名,但手动发送PUT请求时必须自己生成并附加符合AWS v4规范的签名,否则S3会拒绝未授权的请求。另外你的uploadFile函数没有返回Promise,await不会等待上传完成,这也需要修复。
步骤1:修复uploadFile的Promise封装
把回调式的https.request改成Promise,确保await能正确等待上传完成:
const uploadFile = (endpoint, options, buffer) => { return new Promise((resolve, reject) => { const s3UploadRequest = https.request(endpoint, options, (res) => { let responseBody = ''; res.on("data", (chunk) => { responseBody += chunk; }); res.on("end", () => { if (res.statusCode >= 400) { console.log("Upload FAILED", responseBody); reject(new Error(`Upload failed with status ${res.statusCode}: ${responseBody}`)); } else { console.log("Upload complete"); resolve(responseBody); } }); }); s3UploadRequest.on("error", (error) => { console.error(`Error uploading to S3: ${error}`); reject(error); }); s3UploadRequest.write(buffer); s3UploadRequest.end(); }); };
步骤2:生成AWS V4签名
编写函数生成符合AWS签名版本4的认证头,使用Lambda执行角色的临时凭证(Lambda会自动注入这些环境变量):
const crypto = require('crypto'); // 生成AWS V4签名 const generateAwsV4Signature = async (method, url, headers, payload, region, service) => { const accessKey = process.env.AWS_ACCESS_KEY_ID; const secretKey = process.env.AWS_SECRET_ACCESS_KEY; const sessionToken = process.env.AWS_SESSION_TOKEN; const parsedUrl = new URL(url); const host = parsedUrl.host; const path = parsedUrl.pathname; const now = new Date(); const amzDate = now.toISOString().replace(/[:-]/g, '').slice(0, 17) + 'Z'; const dateStamp = amzDate.slice(0, 8); // 1. 创建规范请求 const canonicalHeaders = Object.entries({ 'host': host, ...headers }) .sort(([a], [b]) => a.localeCompare(b)) .map(([key, value]) => `${key.toLowerCase()}:${value.trim()}`) .join('\n'); const signedHeaders = Object.keys({ 'host': host, ...headers }) .sort() .map(key => key.toLowerCase()) .join(';'); const payloadHash = crypto.createHash('sha256').update(payload).digest('hex'); const canonicalRequest = [ method.toUpperCase(), path, '', // 查询参数(这里无) canonicalHeaders, '', signedHeaders, payloadHash ].join('\n'); // 2. 创建待签名字符串 const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`; const stringToSign = [ 'AWS4-HMAC-SHA256', amzDate, credentialScope, crypto.createHash('sha256').update(canonicalRequest).digest('hex') ].join('\n'); // 3. 计算签名 const getSignatureKey = (key, dateStamp, regionName, serviceName) => { const kDate = crypto.createHmac('sha256', `AWS4${key}`).update(dateStamp).digest(); const kRegion = crypto.createHmac('sha256', kDate).update(regionName).digest(); const kService = crypto.createHmac('sha256', kRegion).update(serviceName).digest(); const kSigning = crypto.createHmac('sha256', kService).update('aws4_request').digest(); return kSigning; }; const signingKey = getSignatureKey(secretKey, dateStamp, region, service); const signature = crypto.createHmac('sha256', signingKey).update(stringToSign).digest('hex'); // 4. 生成认证头 const authorizationHeader = `AWS4-HMAC-SHA256 Credential=${accessKey}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`; return { ...headers, 'Authorization': authorizationHeader, 'x-amz-date': amzDate, ...(sessionToken && {'x-amz-security-token': sessionToken}) // Lambda临时凭证需添加此头 }; };
步骤3:修改主代码,添加签名头
在发送请求前调用签名生成函数,把认证头添加到请求选项中:
const buffer = await downloadFile(url); // gz文件下载为buffer const myBucket = '你的桶名'; const myRegion = '你的区域'; const store = '存储路径'; const file_name = '文件名'; const endpoint = `https://${myBucket}.s3.${myRegion}.amazonaws.com/${store}/${file_name}.gz`; const baseHeaders = { "Content-Encoding": "gzip", "Content-Length": Buffer.byteLength(buffer).toString(), "Content-Type": "application/octet-stream", }; // 生成签名头 const signedHeaders = await generateAwsV4Signature('PUT', endpoint, baseHeaders, buffer, myRegion, 's3'); const options = { method: "PUT", headers: signedHeaders, }; const data = await uploadFile(endpoint, options, buffer);
额外检查点
- 确认Lambda执行角色拥有
s3:PutObject权限(你用SDK能正常上传,说明权限没问题,可再核对策略) - 确保
Content-Length的值为字符串类型,避免header格式错误 - 签名生成时要包含所有自定义请求头,严格按照AWS v4规范排序和格式化
内容的提问来源于stack exchange,提问作者Bennyh961
相关产品推荐
相关产品推荐

