You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

不使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 00:47:32