NodeJS 使用带有GET过滤器的Magento Rest API时出现oauth_signature无效错误

nxagd54h  于 2023-02-03  发布在  Node.js
关注(0)|答案(4)|浏览(151)

当我使用http://localhost/magento/api/rest/orders/?filter[1][attribute]=entity_id&filter[1][gt]=70&page=1&limit=100这样的GET过滤器时,我很难从Magento REST API获取订单
它给出了“错误”:[{“代码”:401,“消息”:“oauth_problem=signature_invalid”}]
当我尝试使用Postman这样的REST客户端访问同一个API端点时,我得到了想要的JSON结果。
我怀疑过滤器查询中的方括号可能导致生成Oauth签名时出现问题。所有没有GET过滤器的端点都工作正常。我正在使用请求节点模块发出带有Oauth标头的GET请求。
是否有任何修复程序可以避免签名无效错误?

iqjalb3h

iqjalb3h1#

问题出在我用来生成OAuth签名的请求节点模块中。它没有考虑URL中的方括号。我修改了模块中的代码以包含方括号。更改OAuth签名生成方法为我修复了这个问题

zf2sa74q

zf2sa74q2#

由于我花了一点时间来弄清楚如何做到这一点,我想我会把我学到的东西。我的目标是作出一个单一的请求Magento的REST API返回订单与特定的订单状态。浏览GET过滤器文档页面是不是真的有帮助,直到我看到最后一行。以下是我提出的请求,工作:
http://magentohost.com/api/rest/orders?filter[1][attribute]=status&filter[1][in][1]=pending&filter[1][in][2]=processing上述请求将为您获取状态为"待定"或"正在处理"的所有订单的列表。
参考:http://magentoforce.com/2014/08/magento-rest-api-multiple-in-get-filters/

30byixjq

30byixjq3#

对于任何想要这个问题答案的人来说,这里是对我起作用的:

const axios = require('axios');
const crypto = require('crypto');

exports.handler = async (event) => {
  let base_uri = 'https://YOUR_URL.com'; (ANYTHING UP TO THE /PATH)
  const consumer_key = '';
  const token_key = '';
  const consumer_secret = '';
  const token_secret = '';
==> CHANGE THE QUERY PARAMS TO WHATEVER YOU HAVE <==
  const queryParameters = {
    'searchCriteria[filterGroups][0][filters][0][field]': 'sku',
    'searchCriteria[filterGroups][0][filters][0][condition_type]': 'eq',
    'searchCriteria[filterGroups][0][filters][0][value]': 'aaaaaaaaa',
  };
  const timestamp = Math.floor(Date.now() / 1000);
  const nonce = await generateNonce();

  ==> ADD IN YOUR PATH, IT WILL BE APPENDED TO THE BASE URL <==
  const path = 'products';

  base_uri = `${base_uri}/${path}`;

  const reqsign256 = hmacsign256('GET', base_uri, { ...queryParameters, oauth_consumer_key: consumer_key, oauth_nonce: nonce, oauth_signature_method: 'HMAC-SHA256', oauth_timestamp: timestamp, oauth_token: token_key, oauth_version: '1.0' }, consumer_secret, token_secret);

  const config = {
    method: 'get',
    url: base_uri,
    headers: {
      Authorization: `OAuth oauth_consumer_key="${consumer_key}",oauth_token="${token_key}",oauth_signature_method="HMAC-SHA256",oauth_timestamp="${timestamp}",oauth_nonce="${nonce}",oauth_version="1.0",oauth_signature="${reqsign256}"`
    },
    params: queryParameters,
  };

  let response = {};

  return await axios(config)
    .then(async (response) => {
      response = {
        statusCode: 200,
        body: JSON.stringify(response.data),
      };
      return response;
    })
    .catch((error) => {
      response = {
        statusCode: 200,
        body: JSON.stringify(error.message),
      };
      return response;
    });
};

/**
 *
 * @returns A random string of 11 characters
 */
function generateNonce() {
  let nonce = '';
  const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

  for (let i = 0; i < 11; i++) {
    nonce += possible.charAt(Math.floor(Math.random() * possible.length));
  }

  return nonce;
}

/**
 *
 * @param key
 * @param body
 * @param algorithm
 * @description This is generating the signature using the imported crypto package.
 * @returns The generated signature;
 */
function sha(key, body, algorithm) {
  return crypto.createHmac(algorithm, key).update(body).digest('base64');
}

/**
 *
 * @param str
 * @description The rfc3986 function takes a string as input and returns its encoded representation as per the rules specified in RFC 3986
 * for URI (Uniform Resource Identifier) component encoding. It does this by first using encodeURIComponent to encode the string,
 * which replaces certain characters with their percent-encoded representations. It then replaces characters such as !, *, (, ), and '
 * with their respective percent-encoded representations, which are specified in RFC 3986 as being reserved characters that must be percent-encoded.
 * @returns returns the encoded str value
 */
function rfc3986(str) {
  return encodeURIComponent(str).replace(/!/g, '%21').replace(/\*/g, '%2A').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/'/g, '%27');
}

/**
 *
 * @param obj
 * @description The map function takes an object as input and returns an array of its key-value pairs.
 * It does this by iterating through the properties of the object using a for...in loop, and for each property, it checks its value.
 * If the value is an array, it pushes an array with the property key and each value of the array into the result array.
 * If the value is an object, it pushes an array with a string representation of the property key concatenated with the property name in square brackets
 * and the property value into the result array.
 * If the value is neither an array nor an object, it pushes an array with the property key and value into the result array.
 * Finally, it returns the result array.
 * @returns arr
 */
function map(obj) {
  let key,
    val,
    arr = [];
  for (key in obj) {
    val = obj[key];
    if (Array.isArray(val)) for (let i = 0; i < val.length; i++) arr.push([key, val[i]]);
    else if (typeof val === 'object') for (let prop in val) arr.push([key + '[' + prop + ']', val[prop]]);
    else arr.push([key, val]);
  }
  return arr;
}

/**
 *
 * @param a
 * @param b
 * @description Used to sort the oauth paramters into ascending order -- this is required for oauth1 to work.
 * @returns the comparison result
 */
function compare(a, b) {
  return a > b ? 1 : a < b ? -1 : 0;
}

/**
 *
 * @param httpMethod
 * @param base_uri
 * @param params
 * @description
 * 1.  First, the name and value of each parameter are encoded
 * 2.  The parameters are sorted by name, using ascending byte value ordering. If two or more parameters share the same name, they are sorted by their value.
 * 3.  The name of each parameter is concatenated to its corresponding value using an "=" character (ASCII code 61) as a separator, even if the value is empty.
 * for example in this case it is assigning oauth_nonce = 'foo' or oauth_signature_method = 'HMAC-SHA256' etc
 * 4.  The sorted name/value pairs are concatenated together into single string by using an "&" character (ASCII code 38) as separator.
 * The final output will be something like this:
 * GET&https%3A%2F%2Fstaging2.ospreylondon.com%2Frest%2FV1%2Fproducts&oauth_consumer_key%3Dxxx%26oauth_nonce%3xxxoauth_signature_method%3DHMAC-SHA256%26oauth_timestamp%3Dxxx%26oauth_token%3xxx%26oauth_version%3D1.0%26 ==> There will also be any url paramaters added at the end.
 * @returns base url
 */
function generateBase(httpMethod, base_uri, params) {
  let normalized = map(params)
    // step 1
    .map(function (p) {
      return [rfc3986(p[0]), rfc3986(p[1] || '')];
    })
    // step 2
    .sort(function (a, b) {
      return compare(a[0], b[0]) || compare(a[1], b[1]);
    })
    //step 3
    .map(function (p) {
      return p.join('=');
    })
    //step 4
    .join('&');

  let base = [rfc3986(httpMethod ? httpMethod.toUpperCase() : 'GET'), rfc3986(base_uri), rfc3986(normalized)].join('&');

  return base;
}

/**
 *
 * @param httpMethod
 * @param base_uri
 * @param params
 * @param consumer_secret
 * @param token_secret
 * @description This takes the paramaters passed in, creates a base uri, creates a key (consumer secret and token secret with & between them)
 * @returns this then returns the result of the sha method ==> this is the signature used in the request.
 */
function hmacsign256(httpMethod, base_uri, params, consumer_secret, token_secret) {
  let base = generateBase(httpMethod, base_uri, params);
  let key = [consumer_secret || '', token_secret || ''].map(rfc3986).join('&');

  return sha(key, base, 'sha256');
}

相关问题