亚马逊API MWS 签名不匹配

3 投票
2 回答
2568 浏览
提问于 2025-04-18 18:52

我已经试了好几个小时想让这个工作正常,但其他问题里的解决方案都没用。

我想做的是获取我在亚马逊上的订单列表。为此,我正在调用亚马逊的MWS接口。但是,我收到了这个错误信息:

<?xml version="1.0"?>
<ErrorResponse xmlns="https://mws.amazonservices.com/Orders/2013-09-01">
  <Error>
    <Type>Sender</Type>
    <Code>SignatureDoesNotMatch</Code>
    <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.</Message>
  </Error>
  <RequestID>03686743-15a6-4207-b0b7-316d1e4e5c8f</RequestID>
</ErrorResponse>

为了找出问题出在哪里,我去了亚马逊MWS的Scratchpad,使用了相同的值,甚至是时间戳(我想你有15分钟的时间,过了就失效),然后对比了签名。结果是一样的。不过,我的程序还是出现了同样的错误,而Scratchpad却能正常工作。

这是我所有的代码:

import sys, os, base64, datetime, hashlib, hmac, urllib
from time import gmtime, strftime
from requests import request
import xml.etree.ElementTree as ET

def get_timestamp():
    """Return correctly formatted timestamp"""
    return strftime("%Y-%m-%dT%H:%M:%SZ", gmtime())

def calc_signature():
    """Calculate signature to send with request"""
    sig_data = method + '\n' + domain.replace('https://', '').lower() + '\n' + URI + '\n' + request_description
    hmac_obj = hmac.new(str(SECRET_KEY), sig_data, hashlib.sha256)
    return  urllib.quote(base64.b64encode(hmac_obj.digest()), safe='-_.~')

SECRET_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
AWS_ACCESS_KEY = 'xxxxxxxxxxxxxxxxxxx'
SELLER_ID = 'xxxxxxxxxxxxxxxxxxxxxxx'
MARKETPLACE_ID = 'xxxxxxxxxxxxxxx'

Action = 'ListOrders'
SignatureMethod = 'HmacSHA256'
SignatureVersion = '2'
Timestamp = get_timestamp()
Version = '2013-09-01'
CreatedAfter = '2014-08-26T23:00:57Z' # TODO -1 day
URI = '/Orders/2013-09-01'
domain = 'https://mws.amazonservices.co.uk'
method = 'POST'

payload = {'AWSAccessKeyId': AWS_ACCESS_KEY,
           'Action': Action,
           'SellerId': SELLER_ID,
           'SignatureVersion': SignatureVersion,
           'Timestamp': Timestamp,
           'Version': Version,
           'SignatureMethod': SignatureMethod,
           'CreatedAfter': '2014-08-26T23:00:00Z',
           'MarketplaceId.Id.1': MARKETPLACE_ID
          }

request_description = '&'.join(['%s=%s' % (k, urllib.quote(payload[k], safe='-_.~').encode('utf-8')) for k in sorted(payload)])

sig = calc_signature()

url = '%s%s?%s&Signature=%s' % (domain, URI, request_description, urllib.quote(sig))
headers = {'Host': 'amazonwebservices.co.uk', 'Content-Type': 'text/xml', 'x-amazon-user-agent': 'python-requests/1.2.0 (Language=Python)'}

response = request(method, url, headers=headers)
print response.content

这是在calc_signature()里打印的sig_data

POST
mws.amazonservices.co.uk
/Orders/2013-09-01
AWSAccessKeyId=xxxxxxxxxxxxx&Action=ListOrders&CreatedAfter=2014-08-26T23%3A00%3A00Z&MarketplaceId.Id.1=xxxxxxxxxxxxx&SellerId=xxxxxxxxxxxxxxx&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2014-08-28T15%3A50%3A34Z&Version=2013-09-01

这是打印的url

https://mws.amazonservices.co.uk/Orders/2013-09-01?AWSAccessKeyId=xxxxxxxxxxxx&Action=ListOrders&CreatedAfter=2014-08-26T23%3A00%3A00Z&MarketplaceId.Id.1=xxxxxxxxxxxx&SellerId=xxxxxxxxxxxxx&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2014-08-28T15%3A50%3A34Z&Version=2013-09-01&Signature=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

到现在我完全没有头绪了。我已经三次确认我的密钥、访问密钥、卖家ID和市场ID都是正确的。

任何帮助都将非常非常感激!

2 个回答

2

在你的 calc_signature 函数里面,safe 这个参数没有允许 +/ 这两个字符,而这两个字符在 base64 输出的字符集中是可以用的。因此,它把这两个字符进行了百分号编码,结果在查询字符串中又变成了 %25 这样的形式。

我做了一些额外的修改,所以这里是完整的代码:

import sys, os, base64, datetime, hashlib, hmac, urllib
from time import gmtime, strftime
from requests import request
import xml.etree.ElementTree as ET

def get_timestamp():
    """Return correctly formatted timestamp"""
    return strftime("%Y-%m-%dT%H:%M:%SZ", gmtime())

def calc_signature(method, domain, URI, request_description, key):
    """Calculate signature to send with request"""
    sig_data = method + '\n' + \
        domain.lower() + '\n' + \
        URI + '\n' + \
        request_description

    hmac_obj = hmac.new(key, sig_data, hashlib.sha256)
    digest = hmac_obj.digest()

    return  urllib.quote(base64.b64encode(digest), safe='-_+=/.~')

SECRET_KEY = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
AWS_ACCESS_KEY = 'XXXXXXXXXXXXXX'
SELLER_ID = 'XXXXXXXXXXXXX'
MARKETPLACE_ID = 'XXXXXXXXXXXX'

Action = 'ListOrders'
SignatureMethod = 'HmacSHA256'
SignatureVersion = '2'
Timestamp = get_timestamp()
Version = '2013-09-01'
CreatedAfter = '2013-08-26T23:00:57Z' 
URI = '/Orders/2013-09-01'
domain = 'mws.amazonservices.com'
proto = 'https://'
method = 'POST'

payload = {
    'AWSAccessKeyId': AWS_ACCESS_KEY,
    'Action': Action,
    'SellerId': SELLER_ID,
    'SignatureVersion': SignatureVersion,
    'Timestamp': Timestamp,
    'Version': Version,
    'SignatureMethod': SignatureMethod,
    'CreatedAfter': CreatedAfter,
    'MarketplaceId.Id.1': MARKETPLACE_ID
}

request_description = '&'.join(['%s=%s' % (k, urllib.quote(payload[k], safe='-_.~').encode('utf-8')) for k in sorted(payload)])

sig = calc_signature(method, domain, URI, request_description, SECRET_KEY)

url = '%s%s?%s&Signature=%s' % \
    (proto+domain, URI, request_description, urllib.quote(sig))

headers = {
    'Host': domain,
    'Content-Type': 'text/xml',
    'x-amazon-user-agent': 'python-requests/1.2.0 (Language=Python)'
}

response = request(method, url, headers=headers)

print response.content 
2

我知道这条信息来得有点晚,但我也遇到了同样的问题。尽管我知道我的签名是正确的,但我发现其实问题出在POST请求的执行上,而不是签名本身。如果亚马逊无法理解你的参数,它会返回一个HTTP 403错误,并告诉你签名错误,尽管实际上并不是这样。我不能告诉你为什么会这样,但可以试试不使用requests库,而是用urllib.request来处理,先构建好你的URL,然后再执行,这对我有用:

#using python 3.4

import urllib.request

#... your code from before...

headers = {'Host': 'mws.amazonservices.com', 'Content-Type': 'text/xml', 'x-amazon-user-agent': 'SomeApp/1.1 (Language=Python)'}
req = urllib.request.Request(method=method,url=url,headers=headers)
response = urllib.request.urlopen(req)
the_page = response.read()
print(the_page)

#As seen here: https://docs.python.org/3/howto/urllib2.html#data

如果这样还是不行,建议再仔细检查一下你编码的方式,可能需要尝试一下urllib.parse。

祝你在亚马逊上顺利!

撰写回答