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

使用AWS Signature Version 4签名POST请求遇403,如何正确实现?

解决AWS SigV4 POST请求签名的哈希与编码问题

核心问题定位

你碰到的403错误,核心原因几乎肯定是请求体哈希计算不符合规范,或者签名流程中的字符编码/格式处理和官方要求不一致——Postman会严格遵循SigV4标准处理,而手动转写JS代码时,容易在编码细节上出错。

正确的SigV4 POST签名关键步骤及代码实现

1. 计算请求体的SHA-256哈希

这是最容易出错的环节,必须严格遵守:

  • 空请求体必须使用固定哈希值:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
  • 非空请求体必须基于原始二进制数据计算哈希,不能先转成字符串再处理;浏览器环境要注意Blob/FormData的读取方式
  • 计算结果必须转成小写十六进制字符串

Node.js 实现:

const crypto = require('crypto');

function calculatePayloadHash(payload) {
  if (!payload || payload.length === 0) {
    return 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
  }
  return crypto.createHash('sha256').update(payload).digest('hex').toLowerCase();
}

浏览器环境(使用Web Crypto API):

async function calculatePayloadHash(payload) {
  if (!payload) {
    return 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
  }
  const encoder = new TextEncoder();
  const data = encoder.encode(payload);
  const hashBuffer = await crypto.subtle.digest('SHA-256', data);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  return hashArray.map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase();
}

2. 构建规范请求字符串(Canonical Request)

必须严格按照SigV4规则拼接,注意细节:

  • 每行末尾必须是LF换行符(\n),不能用CRLF(\r\n)
  • 请求头需按字母排序后拼接,格式必须是key:value(不能有多余空格)
  • 必须包含host和x-amz-date头;POST请求若有请求体,需包含content-type头
  • 最后一行是请求体的哈希值

示例代码:

function buildCanonicalRequest(method, url, headers, payloadHash) {
  const parsedUrl = new URL(url);
  const canonicalUri = encodeURIComponent(parsedUrl.pathname).replace(/%2F/g, '/');
  const canonicalQuerystring = parsedUrl.search.slice(1);

  // 排序并格式化请求头
  const sortedHeaders = Object.keys(headers).sort().map(key => `${key.toLowerCase()}:${headers[key].trim()}`).join('\n');
  const signedHeaders = Object.keys(headers).sort().map(key => key.toLowerCase()).join(';');

  return [
    method,
    canonicalUri,
    canonicalQuerystring,
    sortedHeaders,
    '', // 空行分隔请求头与哈希值
    signedHeaders,
    payloadHash
  ].join('\n');
}

3. 构建待签名字符串(String to Sign)

注意格式要求:

  • x-amz-date必须是YYYYMMDD'T'HHMMSS'Z'格式(比如20240520T123456Z)
  • 凭证范围格式为YYYYMMDD/region/service/aws4_request

示例代码:

function buildStringToSign(canonicalRequest, amzDate, credentialScope) {
  const canonicalRequestHash = crypto.createHash('sha256').update(canonicalRequest).digest('hex').toLowerCase();
  return [
    'AWS4-HMAC-SHA256',
    amzDate,
    credentialScope,
    canonicalRequestHash
  ].join('\n');
}

4. 计算签名密钥与最终签名

需通过HMAC-SHA256逐步推导签名密钥:

function getSignatureKey(secretKey, dateStamp, regionName, serviceName) {
  const kDate = crypto.createHmac('sha256', `AWS4${secretKey}`).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;
}

function calculateSignature(stringToSign, signatureKey) {
  return crypto.createHmac('sha256', signatureKey).update(stringToSign).digest('hex').toLowerCase();
}

浏览器环境额外注意事项

  • 绝对不能在浏览器中暴露AWS永久密钥,建议通过后端代理完成签名,或使用AWS Cognito临时凭证
  • 确保请求体的序列化格式和Postman完全一致(比如JSON要严格序列化,不能有多余空格)
  • 检查x-amz-date和dateStamp的格式:dateStamp是纯日期YYYYMMDD,不含时间部分

验证方法

把你的代码生成的内容和Postman对比:

  1. 在Postman中开启AWS Signature授权,发送请求后查看Authorization头,拆分出凭证范围、签名头、签名值
  2. 对比代码生成的规范请求字符串和Postman内部生成的版本(可通过Postman的“代码生成”功能导出curl,反向推导规范请求)
  3. 重点核对请求体哈希值、换行符格式、请求头排序这几个环节

内容的提问来源于stack exchange,提问作者JeremiahDuane

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 09:03:50