headers 和 get_quote 中的 oauth for E*Trade API 使用 Python3

headers and oauth in get_quote for E*Trade API using Python3

授权我的应用程序后,我通过 header 传递 oauth 凭据来请求访问令牌。签名和header就是通过这段代码生成的;

API_module = 'oauth'
API_RESTful = 'access_token'
if renewal:
    API_RESTful = 'renew_access_token'
production_url = 'https://etws.etrade.com/{0:s}/{1:s}'.format(API_module, API_RESTful)

oauth_timestamp = int(time.time())
rand_str = lambda n: ''.join([random.choice(string.hexdigits) for i in range(n)])
oauth_nonce = rand_str(40)
key = oauth_consumer_secret + \
      '&' + \
      quote_plus(oauth_token_secret)
base_string = quote_plus('GET') + '&' + \
              quote_plus('https://etws.etrade.com/oauth/access_token') + '&' + \
              quote_plus('oauth_consumer_key={}&'.format(oauth_consumer_key)) + \
              quote_plus('oauth_nonce={}&'.format(oauth_nonce)) + \
              quote_plus('oauth_signature_method=HMAC-SHA1&') + \
              quote_plus('oauth_timestamp={:d}&'.format(oauth_timestamp)) + \
              quote_plus('oauth_token={}&'.format(quote_plus(oauth_token))) + \
              quote_plus('oauth_verifier={}'.format(oauth_verification_code))
hashed = hmac.new(key.encode(), base_string.encode(), sha1)
oauth_signature = quote_plus(binascii.b2a_base64(hashed.digest())[:-1])
header_string = 'Authorization: OAuth ' + \
                'realm="",' + \
                'oauth_signature="{}",'.format(oauth_signature) + \
                'oauth_nonce="{}",'.format(quote_plus(oauth_nonce)) + \
                'oauth_signature_method="{}",'.format(oauth_signature_method) + \
                'oauth_consumer_key="{}",'.format(oauth_consumer_key) + \
                'oauth_timestamp="{}",'.format(str(oauth_timestamp)) + \
                'oauth_verifier="{}",'.format(oauth_verification_code) + \
                'oauth_token="{}"'.format(quote_plus(oauth_token))
headers_list.append(header_string)

response = curl_get_http(current_url=production_url)

产生这些 header;

Host: etws.etrade.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Connection: keep-alive
Authorization: OAuth
realm="",
oauth_signature="fzqLbI8LBlBGs1Clp4eAgs09YuM%3D",
oauth_nonce="E447Ea1FCfbcCF0116fbdC47bE8E4aA4Cf7e3Aab",
oauth_signature_method="HMAC-SHA1",
oauth_consumer_key="4b6471c7ee",
oauth_timestamp="1501003943",
oauth_verifier="O5K2A",
oauth_token="BVuKV9Q7F93OxjbqY%2FzRmoqI0M%3D"

请求returns;

oauth_token=3W3hs5aSQPwMR%2FM0H0%2BPWhI%2Bo%3D&oauth_token_secret=SWvknmgEIgKzbN35bwwoNw%3D

通过用新的 token_secret 替换旧的 token_secret 来更新密钥。基础字符串也用新的令牌值和新的时间戳和随机数更新。这些新值用于生成新签名。

url_quotes = 'https://etws.etrade.com/market/rest/quote/{0:s}?detailFlag={1:s}'.format(symbols, flag)

oauth_timestamp = int(time.time())
rand_str = lambda n: ''.join([random.choice(string.hexdigits) for i in range(n)])
oauth_nonce = rand_str(40)

key = oauth_consumer_secret + \
      '&' + \
      oauth_token_secret
base_string = quote_plus('GET') + '&' + \
              quote_plus('https://etws.etrade.com/market/rest/quote') + '&' + \
              quote_plus('oauth_consumer_key={}&'.format(oauth_consumer_key)) + \
              quote_plus('oauth_nonce={}&'.format(oauth_nonce)) + \
              quote_plus('oauth_signature_method=HMAC-SHA1&') + \
              quote_plus('oauth_timestamp={:d}&'.format(oauth_timestamp)) + \
              quote_plus('oauth_token={}&'.format(oauth_token)) #+ \
              #quote_plus('oauth_verifier={}'.format(oauth_verification_code))
hashed = hmac.new(key.encode(), base_string.encode(), sha1)
oauth_signature = quote_plus(binascii.b2a_base64(hashed.digest())[:-1])
header_string = 'Authorization: OAuth ' + \
                'realm="",' + \
                'oauth_signature="{}",'.format(oauth_signature) + \
                'oauth_nonce="{}",'.format(quote_plus(oauth_nonce)) + \
                'oauth_signature_method="{}",'.format(oauth_signature_method) + \
                'oauth_consumer_key="{}",'.format(oauth_consumer_key) + \
                'oauth_timestamp="{:d}",'.format(oauth_timestamp) + \
                'oauth_verifier="{}",'.format(oauth_verification_code) + \
                'oauth_token="{}"'.format(oauth_token)
headers_list.append(header_string)
response = curl_get_http(current_url=url_quotes)

将 header 更改为;

Host: etws.etrade.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Connection: keep-alive
Authorization: OAuth
realm="",
oauth_signature="v4xa%2FrCKtFRSUdHw%3D",
oauth_nonce="57FCC260F81b2fAd95AccA69FE07BFFcd06d83AB",
oauth_signature_method="HMAC-SHA1",
oauth_consumer_key="4b6471c7ee",
oauth_timestamp="1501003945",
oauth_verifier="O5K2A",
oauth_token="3W3hs5aSQPwMR%2FM0H0%2BPWhI%2Bo%3D"

并发出了 get_quote 请求;

https://etws.etrade.com/market/rest/quote/TICK,ER,THAT,GOES,UP?detailFlag=FUNDAMENTAL

不过,返回的不是引号,而是 oauth 问题。

<Error>
<message>oauth_problem=signature_invalid</message>
</Error>

我试过在url中传递信息,但是returns同样的错误。请求中是否存在程序错误?是否应该在不更新签名的情况下使用新令牌?

(已更改发布的凭据以保护无辜者)

问题出在签名生成中。

对于 URL 部分,您应该包括完整的 URL 直到查询字符串。在这个例子中它将是:

base_string = quote_plus('GET') + '&' + \
          quote_plus('https://etws.etrade.com/market/rest/quote/TICK,ER,THAT,GOES,UP') + '&' + \

连接 oauth 参数时,您还应包括所有 non-Oauth 查询参数。它们需要按照规范 (https://oauth.net/core/1.0a/#anchor13) 进行排序。在这种情况下,您需要包含 detailFlag=FUNDAMENTAL 作为您的第一个参数:

quote_plus('detailFlag=FUNDAMENTAL&') + \ 
quote_plus('oauth_consumer_key={}&'.format(oauth_consumer_key)) + \

此外,header 中不需要 oauth_verifier 除了检索访问令牌。