它给出了“错误”:[{“code”:401,“message”:“oauth_problem=signature_invalid”}]
当我尝试使用 Postman 等 REST 客户端访问相同的 API 端点时,我会返回所需的 JSON 结果。
我怀疑过滤器查询中的方括号可能会导致生成 Oauth 签名时出现问题。所有没有 GET 过滤器的端点都工作正常。 我正在使用请求节点模块来发出带有 oauth 标头的 GET 请求。
是否有任何修复可以避免签名无效错误?
问题出在我用来生成 OAuth 签名的请求节点模块中。它没有考虑 URL 中的方括号。我修改了模块中的代码以包含方括号。更改 OAuth 签名生成方法为我解决了这个问题
对于任何想要答案的人来说,这对我有用:
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');
}
由于我花了一些时间才弄清楚如何做到这一点,所以我想我会传承我学到的东西。我的目标是向 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/