Google Apps 脚本:从亚马逊销售合作伙伴 API 获取订单(签名请求)

时间:2021-03-10 01:20:46

标签: amazon-web-services api google-apps-script

我正在尝试按照此 guide 创建对亚马逊销售合作伙伴 API 的请求。

第一部分:here 已经完成了创建访问权限。

订单 API 的文档可以在 here 中找到。

我正在尝试调用 GET /orders/v0/orders 操作。

连接到 API

此操作的唯一必需参数是基于文档的 MarketplaceIds

为了获得订单,我们需要签署我们的请求。到目前为止,这是我的代码:

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, "");//NEW
  requestPayloadHashed = requestPayloadHashed.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");//NEW

  //Building the canonical request
  var canonical_string = httpRequestMethod + '\n' + canonicalURI + '\n' + canonicalQueryString + '\n' + canonicalheaders + '\n' + signedheaders + '\n' + requestPayloadHashed;//UPDATED
  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);//NEW

  //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
      //Path parameter not needed
    },
    'headers': {
      //'host': end_point,
      '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);
}

问题

运行脚本时出现以下错误:

    Exception: Request failed for https://sellingpartnerapi-eu.amazon.com returned code 403. Truncated server response: {
{
  "errors": [
    {
      "message": "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.

The Canonical String for this request should have been
'POST
/

host:sellingpartnerapi-eu.amazon.com
user-agent:Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; +https://script.google.com; id: UAEmdDd-KyWEWcR137UzUzWb1fu3rUgNviHA)
x-amz-access-token:Atza|IwEBSomeAccessToken
x-amz-date:2021-03-10T02:44:01.727Z

host;user-agent;x-amz-access-token;x-amz-date
cf22942946358a7530d8b72df6333e859644aaebb08a1cd825a6af65a8561111'

The String-to-Sign should have been
'AWS4-HMAC-SHA256
20210310T024401Z
20210310/eu-west-1/execute-api/aws4_request
c4c1dcea7026765f52c5265296f9e1cb91b6618928debbc04a393bac89ce8493'
",
     "code": "InvalidSignature"
    }
  ]
}

问题

我对什么是“有效载荷”有很大的疑问

对于这部分代码:

var canonical_string = httpRequestMethod + '\n' + canonicalURI + '\n' + canonicalQueryString + '\n' + canonicalheaders + '\n' + signedheaders + '\n' + requestPayloadHashed;

我们必须合并有效负载请求 requestPayloadHashed 的散列版本。

它还提到:

<块引用>

如果有效负载为空,则使用空字符串作为哈希的输入 功能。

现在我只是用一个空值创建了那个变量

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

但我不确定我是否省略了一些重要的内容。

更新 #1

应用 Tanaike 建议后,我收到以下消息:

{
  "errors": [
    {
      "message": "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.

The Canonical String for this request should have been
'GET
/orders/v0/orders
marketplaceId=A1PA6795UKMFR9
host:sellingpartnerapi-eu.amazon.com
user-agent:Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; +https://script.google.com; id: UAEmdDd-KyWEWcR137UzUzWb1fu3rUgNviHA)
x-amz-access-token:Atza|IwEBISomeAccessToken
x-amz-date:2021-03-10T03:00:14.411Z

host;user-agent;x-amz-access-token;x-amz-date
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'

The String-to-Sign should have been
'AWS4-HMAC-SHA256
20210310T030014Z
20210310/eu-west-1/execute-api/aws4_request
f1bbc99190ca5a9e9e068ad6a0b2ef6a7aed4a1232095ef8f3d77ad62d0e66ac'
",
     "code": "InvalidSignature"
    }
  ]
}

更新 #2 有这个网站可以帮助我们对这些连接进行一些测试:

https://mws.amazonservices.de/scratchpad/index.html

通过使用它,我相信我已经验证了 Access Key IDSecret Key,但是,它要求提供一个对我来说是新的且在 API docs 中也未提及的 SellerId。

我想知道它可以去哪里。

enter image description here

更新 #3

我实施了大部分 Tanaike 建议,并尝试将我发送到 API 的内容与我收到的错误消息保持一致:

这是脚本的最后一个版本:

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';//;user-agent
  var requestPayloadHashed = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, "");//NEW
  requestPayloadHashed = requestPayloadHashed.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");//NEW

  //Building the canonical request
  var canonical_string = httpRequestMethod + '\n' + canonicalURI + '\n' + "marketplaceId=A1PA6795UKMFR9" + '\n' + canonicalheaders + '\n\n' + signedheaders + '\n' + requestPayloadHashed;//UPDATED
  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': {
      //'host': end_point,
      'x-amz-access-token': access_token,
      'x-amz-date': isoDate,
      //'user-agent': 'Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; +https://script.google.com; id: UAEmdDd-KyWEWcR137UzUzWb1fu3rUgNviHA)',
      '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": ""
    }
  ]
}

不过,这可能是因为我在注册应用程序(指南 here)时使用了 IAM 用户而不是 IAM 角色。

它在指南中说:

<块引用>

重要。注册您的应用程序时,您使用的 IAM ARN 必须为您附加 IAM 的 IAM 实体提供 步骤 3. 创建 IAM 策略中的策略。在此工作流程中,IAM entity 是第 4 步中的 IAM 角色。创建 IAM 角色。如果你 使用您的 IAM 用户注册您的应用程序,请确保 IAM 政策附在它上面。否则您致电销售合作伙伴 API 将失败。我们建议使用 IAM 注册您的应用程序 角色,如本工作流程所示,帮助您更好地控制对 您的 AWS 资源。

所以我将继续解决这个问题,看看我是否获得了所需的授权。

1 个答案:

答案 0 :(得分:1)

修改点:

  • UrlFetchApp 的情况下,当使用 payload 时,即使 methodGET,它也会作为 POST 请求被请求。似乎这是当前的规范。
  • user-agent 不能更改为 UrlFetchApp

作为前提,当您的授权值是向端点请求的正确值时,您的脚本可以通过上述几点进行修改,如下所示。

我认为您的错误消息可能是由于“GET”和“POST”方法之间的差异造成的。首先,请测试以下修改。发生错误时,请显示。

修改后的脚本:

从:
var options = {
  'method': 'GET',
  'payload': {
    'end_point': end_point,
    'path': canonicalURI,
    'query_string': canonicalQueryString
    //Path parameter not needed
  },
  'headers': {
    //'host': end_point,
    '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);
到:
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,
  },
}

var getOrders = UrlFetchApp.fetch(end_point + canonicalURI + canonicalQueryString, options);

参考:

添加:

Signing AWS requests with Signature Version 4 开始,我修改了您的脚本。当我看到你的脚本时,我注意到字节数组包含在字符串值中。我认为这可能也是您的问题的原因之一。所以我修改了你的脚本。你能确认一下吗?并且看到官方文档,确认了当字节数组用于Utilities.computeHmacSha256Signature时,将字符串值转换为字节数组与样本相同,而不是将字节数组转换为字符串值。

function sample() {
  const hex = bytes => bytes.map(byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
  const digestToHex = data => hex(Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, data));
  const toBytes = data => Utilities.newBlob(data).getBytes();

  const ACCESS_ID = "MyAccessKey";
  const ACCESS_KEY = "MyAccessSecret";
  var access_token = "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";

  // 1. Create string to sign.
  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';
  const canonicalRequest = [httpRequestMethod,canonicalURI,canonicalQueryString,canonicalheaders + "\n",signedheaders,digestToHex("")].join("\n");
  const canonical_request = digestToHex(canonicalRequest);
  var credential_scope = yearMonthDay + '/' + aws_region + '/' + service + '/' + termination_string;
  var string_to_sign = "AWS4-HMAC-SHA256" + '\n' + isoDate + '\n' + credential_scope + '\n' + canonical_request;

  // 2. Create derived signing key.
  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);

  // 3. Create signature.
  const signature = hex(Utilities.computeHmacSha256Signature(toBytes(string_to_sign), kSigning));

  // 4. Request.
  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,
    },
  }
  var getOrders = UrlFetchApp.fetch(end_point + canonicalURI + canonicalQueryString, options);
  Logger.log(getOrders);
}

注意:

  • 不幸的是,我无法测试以上修改后的脚本。所以当您测试它时出现错误,请确认您的值以再次授权。并请显示错误信息。

  • 在此修改中,它假设您的授权值是正确的值。请注意这一点。

  • 关于const canonicalRequest = [httpRequestMethod,canonicalURI,canonicalQueryString,canonicalheaders + "\n",signedheaders,digestToHex("")].join("\n");,当上述脚本出现错误时,请测试const canonicalRequest = [httpRequestMethod,canonicalURI,canonicalQueryString,canonicalheaders + "\n",""].join("\n");

参考: