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

Google Apps Script调用亚马逊SP-API获取订单时签名及授权异常排查

Google Apps Script 调用亚马逊SP-API订单接口问题排查过程

开发前置情况

  • 目标是基于官方开发者指南要求,开发Google Apps Script脚本,调用GET /orders/v0/orders接口拉取订单数据
  • 目前已完成access_token的生成逻辑,按照接口文档说明,MarketplaceIds是该接口唯一必填参数
  • 因SP-API强制要求AWS V4签名校验,先编写了初版脚本发起调用

初版参考脚本

function GetOrders(){
  var access_token = AccessToken();

  //Time variables
  var currentDate = new Date();
  var isoDate = currentDate.toISOString();
  var yearMonthDay= Utilities.formatDate(currentDate, 'GTM-5', 'yyyyMMdd');

  //API variables
  var end_point = 'https://sellingpartnerapi-eu.amazon.com';

  //Credential variables
  var aws_region = "eu-west-1";
  var service = "execute-api";
  var termination_string = "aws4_request";

  //CanonicalRequest = httpRequestMethod + '\n' + CanonicalURI + '\n' + CanonicalQueryString + '\n' + CanonicalHeaders + '\n' + SignedHeaders + '\n' + HexEncode(Hash(RequestPayload));
  //CanonicalRequest components:
  var httpRequestMethod = 'GET';
  var canonicalURI = '/orders/v0/orders';
  var canonicalQueryString = '?marketplaceId=A1PA6795UKMFR9';
  var canonicalheaders = 'host:' + canonicalURI + '\n' + 'x-amz-access-token:' + access_token + '\n' + 'x-amz-date:' + isoDate;
  var signedheaders = 'host;user-agent;x-amz-access-token;x-amz-date';
  var requestPayloadHashed = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, "");
  requestPayloadHashed = requestPayloadHashed.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");

  //Building the canonical request
  var canonical_string = httpRequestMethod + '\n' + canonicalURI + '\n' + canonicalQueryString + '\n' + canonicalheaders + '\n' + signedheaders + '\n' + requestPayloadHashed;
  var canonical_signature = Utilities.computeHmacSha256Signature(canonical_string, ACCESS_KEY);
  var canonical_request = canonical_string + '\n' + canonical_signature;
  canonical_request = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, canonical_request);

  //CredentialScope = Date + AWS region + Service + Termination string;
  //StringToSign = Algorithm + \n + RequestDateTime + \n + CredentialScope + \n + HashedCanonicalRequest;
  var credential_scope = yearMonthDay + '/' + aws_region + '/' + service + '/' + termination_string;
  var string_to_sign = "AWS4-HMAC-SHA256" + '\n' + isoDate + '\n' + credential_scope + '\n' + canonical_request;

  var kSecret = ACCESS_KEY;
  var kDate = Utilities.computeHmacSha256Signature(yearMonthDay, "AWS4" + kSecret);
  var kRegion = Utilities.computeHmacSha256Signature(Utilities.newBlob(aws_region).getBytes(), kDate);
  var kService = Utilities.computeHmacSha256Signature(Utilities.newBlob(service).getBytes(), kRegion);
  var kSigning = Utilities.computeHmacSha256Signature(Utilities.newBlob(termination_string).getBytes(), kService);
  kSigning = kSigning.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");
  Logger.log('kSigning: ' + kSigning);

  var signature = Utilities.computeHmacSha256Signature(kSigning, string_to_sign);
  signature = signature.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");

  var options = {
    'method': 'GET',
    'payload': {
      'end_point': end_point,
      'path': canonicalURI,
      'query_string': canonicalQueryString
    },
    'headers': {
      'x-amz-access-token': access_token,
      'x-amz-date': isoDate,
      'user-agent': 'GAS Script 1.0 (Javascript)',
      'Authorization': 'AWS4-HMAC-SHA256 Credential=' + ACCESS_ID + '/' + credential_scope + ', SignedHeaders=' + signedheaders + ', Signature=' + signature,
    },
  }
  
  var getOrders = UrlFetchApp.fetch(end_point, options);
  Logger.log(getOrders);
}

第一次报错:InvalidSignature

运行初版脚本时返回403错误,服务端返回InvalidSignature提示,即客户端计算的签名和服务端校验的签名不匹配,同时服务端给出了它端生成的规范请求串、待签名串作为对照。
关于空请求载荷的处理存在疑问:按照AWS签名规则,规范请求串的最后一部分是请求载荷的SHA256哈希值,空载荷需要用空字符串作为哈希输入,当前实现的空GET请求哈希计算逻辑如下,不确定是否遗漏关键步骤:

var requestPayloadHashed = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, "");
requestPayloadHashed = requestPayloadHashed.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");

第一次调整后仍报签名不匹配

对照服务端给出的规范串调整签名计算逻辑后,依然返回InvalidSignature错误。
这期间用亚马逊官方SP-API测试工具验证了手中的Access Key ID和Secret Key有效性,发现工具要求传入订单接口文档中未提及的SellerId参数,暂时不清楚该参数的作用与使用场景。

第二次调整后报错:Unauthorized

按照服务端给出的规范要求,对齐了时间格式、规范请求串拼接规则、签名派生逻辑后,调整后的脚本如下:

function GetOrders(){
  var access_token = AccessToken();

  //Time variables
  var currentDate = new Date();
  var isoDate = currentDate.toISOString();
  var isoString = isoDate.replace(/-/g, "").replace(/:/g, "").replace(/(\.\d{3})/, "");
  var yearMonthDay= Utilities.formatDate(currentDate, 'GTM-5', 'yyyyMMdd');
  Logger.log('isoDate: ' + isoDate)
  //API variables
  var end_point = 'https://sellingpartnerapi-eu.amazon.com';

  //Credential variables
  var aws_region = "eu-west-1";
  var service = "execute-api";
  var termination_string = "aws4_request";

  //CanonicalRequest = httpRequestMethod + '\n' + CanonicalURI + '\n' + CanonicalQueryString + '\n' + CanonicalHeaders + '\n' + SignedHeaders + '\n' + HexEncode(Hash(RequestPayload));
  //CanonicalRequest components:
  var httpRequestMethod = 'GET';
  var canonicalURI = '/orders/v0/orders';
  var canonicalQueryString = '?marketplaceId=A1PA6795UKMFR9';
  var canonicalheaders = 'host:' + "sellingpartnerapi-eu.amazon.com" + '\n' + 'x-amz-access-token:' + access_token + '\n' + 'x-amz-date:' + isoDate;
  var signedheaders = 'host;x-amz-access-token;x-amz-date';
  var requestPayloadHashed = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, "");
  requestPayloadHashed = requestPayloadHashed.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");

  //Building the canonical request
  var canonical_string = httpRequestMethod + '\n' + canonicalURI + '\n' + "marketplaceId=A1PA6795UKMFR9" + '\n' + canonicalheaders + '\n\n' + signedheaders + '\n' + requestPayloadHashed;
  Logger.log('canonical_string: ' + canonical_string)
  var canonical_signature = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, canonical_string);
  canonical_request = canonical_signature.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");
  Logger.log("canonical_request: " + canonical_request)

  //CredentialScope = Date + AWS region + Service + Termination string;
  //StringToSign = Algorithm + \n + RequestDateTime + \n + CredentialScope + \n + HashedCanonicalRequest;
  var credential_scope = yearMonthDay + '/' + aws_region + '/' + service + '/' + termination_string;
  var string_to_sign = "AWS4-HMAC-SHA256" + '\n' + isoString + '\n' + credential_scope + '\n' + canonical_request;
  Logger.log("string_to_sign: " + string_to_sign);
  var kSecret = ACCESS_KEY;
  var kDate = Utilities.computeHmacSha256Signature(yearMonthDay, "AWS4" + kSecret);
  var kRegion = Utilities.computeHmacSha256Signature(toBytes(aws_region), kDate);
  var kService = Utilities.computeHmacSha256Signature(toBytes(service), kRegion);
  var kSigning = Utilities.computeHmacSha256Signature(toBytes(termination_string), kService);
  Logger.log('kSigning: ' + kSigning);

  var signature = hex(Utilities.computeHmacSha256Signature(toBytes(string_to_sign), kSigning));
  Logger.log('signature: ' + signature)
  var options = {
    'method': 'GET',
    'headers': {
      'x-amz-access-token': access_token,
      'x-amz-date': isoDate,
      'Authorization': 'AWS4-HMAC-SHA256 Credential=' + ACCESS_ID + '/' + credential_scope + ', SignedHeaders=' + signedheaders + ', Signature=' + signature,
    },
    'muteHttpExceptions': true
  }
  
  var getOrders = UrlFetchApp.fetch(end_point + canonicalURI + canonicalQueryString, options);
  Logger.log(getOrders);
}

调整后脚本不再触发签名校验错误,新的服务端响应为未授权访问:

{
  "errors": [
    {
      "message": "Access to requested resource is denied.",
      "code": "Unauthorized",
      "details": ""
    }
  ]
}

当前问题初步定位

根据官方应用注册规则,注册应用时填写的IAM ARN必须是绑定了对应权限策略的IAM角色,不能填写IAM用户的ARN,否则SP-API请求会直接被拒绝。目前初步判断这个未授权错误是注册应用时误填了IAM用户的ARN导致的,准备修正该配置后再次测试。


内容的提问来源于stack exchange,提问作者Luis Alberto Delgado de la Flo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.31 15:27:04