如何在 Auth object 的 __call__ 方法中签署 requests.Request 的 body?
How do I sign the body of a requests.Request in an Auth object's __call__ method?
我正在尝试为 kraken 编写一个不错的身份验证助手。我希望它尽可能自动,所以它需要:
- 向 POST body
添加随机数 (time.time()*1000
)
- 计算 POST body
上的签名
- 将签名放入headers
我根据答案写了明显的代码:
class KrakenAuth(AuthBase):
"""a requests-module-compatible auth module for kraken.com"""
def __init__(self, key, secret):
self.api_key = key
self.secret_key = secret
def __call__(self, request):
#print("Auth got a %r" % type(request))
nonce = int(1000*time.time())
request.data = getattr(request, 'data', {})
request.data['nonce'] = nonce
request.prepare()
message = request.path_url + hashlib.sha256(str(nonce) + request.body).digest()
hmac_key = base64.b64decode(self.secret_key)
signature = hmac.new(hmac_key, message, hashlib.sha512).digest()
signature = base64.b64encode(signature)
request.headers.update({
'API-Key': self.api_key,
'API-Sign': signature
})
return request
我正在调用它们(从另一个 object 上的包装方法),例如:
def _request(self, method, url, **kwargs):
if not self._auth:
self._auth = KrakenAuth(key, secret)
if 'auth' not in kwargs:
kwargs['auth'] = self._auth
return self._session.request(method, URL + url, **kwargs)
...但它不起作用。 commented-out print()
语句显示它得到的是 PreparedRequest
object 而不是 Request
object,因此调用 request.prepare()
是对 PreparedRequest.prepare
的调用 nothing useful 因为没有 request.data
因为它已经被转换成 body
属性。
您无法访问请求的 data
属性,因为身份验证对象应用于 requests.PreparedRequest()
instance, 没有 .data
属性.
Session.request()
call(所有 request.<method>
和 session.<method>
调用都使用)的正常流程如下:
- 一个
Request()
实例使用与原始调用相同的所有参数创建
- 请求被传递给
Session.prepare_request()
,它首先将会话存储的基值与原始调用的参数合并,然后
- 创建了一个
PreparedRequest()
instance
- 在准备好的请求实例上调用
PreparedRequest.prepare()
method,传入来自 Request
实例和会话的合并数据。
prepare()
方法调用各种 self.prepare_*
方法,包括 PreparedRequest.prepare_auth()
.
PreparedRequest.prepare_auth()
调用 auth(self)
使身份验证对象有机会向请求附加信息。
不幸的是,在此流程中,除了 PreparedRequest.prepare()
和 PreparedRequest.prepare_body()
之外,其他任何人都无法使用原始 data
映射,并且在这些方法中,映射是局部变量。您无法从身份验证对象访问它。
那么您的选择是:
再次解码正文,并用更新后的映射调用prepare_body()
。
不使用身份验证对象,而是使用我回答中的其他路径;显式创建准备好的请求并首先操作 data
。
使用 Python 堆栈玩快乐地狱,并从两帧以上的 prepare()
方法中提取局部变量。我真的不能推荐这条路。
为了更好地封装身份验证方法,我会进行解码/重新编码;后者通过重用 PreparedRequest.prepare_body()
:
就足够简单了
import base64
import hashlib
import hmac
import time
try:
# Python 3
from urllib.parse import parse_qs
except ImportError:
# Python 2
from urlparse import parse_qs
from requests import AuthBase
URL_ENCODED = 'application/x-www-form-urlencoded'
class KrakenAuth(AuthBase):
"""a requests-module-compatible auth module for kraken.com"""
def __init__(self, key, secret):
self.api_key = key
self.secret_key = secret
def __call__(self, request):
ctheader = request.headers.get('Content-Type')
assert (
request.method == 'POST' and (
ctheader == URL_ENCODED or
requests.headers.get('Content-Length') == '0'
)
), "Must be a POST request using form data, or empty"
# insert the nonce in the encoded body
data = parse_qs(request.body)
data['nonce'] = nonce
request.prepare_body(data, None, None)
body = request.body
if not isinstance(body, bytes): # Python 3
body = body.encode('latin1') # standard encoding for HTTP
message = request.path_url + hashlib.sha256(b'%s%s' % (nonce, body)).digest()
hmac_key = base64.b64decode(self.secret_key)
signature = hmac.new(hmac_key, message, hashlib.sha512).digest()
signature = base64.b64encode(signature)
request.headers.update({
'API-Key': self.api_key,
'API-Sign': signature
})
return request
我正在尝试为 kraken 编写一个不错的身份验证助手。我希望它尽可能自动,所以它需要:
- 向 POST body 添加随机数 (
- 计算 POST body 上的签名
- 将签名放入headers
time.time()*1000
)
我根据
class KrakenAuth(AuthBase):
"""a requests-module-compatible auth module for kraken.com"""
def __init__(self, key, secret):
self.api_key = key
self.secret_key = secret
def __call__(self, request):
#print("Auth got a %r" % type(request))
nonce = int(1000*time.time())
request.data = getattr(request, 'data', {})
request.data['nonce'] = nonce
request.prepare()
message = request.path_url + hashlib.sha256(str(nonce) + request.body).digest()
hmac_key = base64.b64decode(self.secret_key)
signature = hmac.new(hmac_key, message, hashlib.sha512).digest()
signature = base64.b64encode(signature)
request.headers.update({
'API-Key': self.api_key,
'API-Sign': signature
})
return request
我正在调用它们(从另一个 object 上的包装方法),例如:
def _request(self, method, url, **kwargs):
if not self._auth:
self._auth = KrakenAuth(key, secret)
if 'auth' not in kwargs:
kwargs['auth'] = self._auth
return self._session.request(method, URL + url, **kwargs)
...但它不起作用。 commented-out print()
语句显示它得到的是 PreparedRequest
object 而不是 Request
object,因此调用 request.prepare()
是对 PreparedRequest.prepare
的调用 nothing useful 因为没有 request.data
因为它已经被转换成 body
属性。
您无法访问请求的 data
属性,因为身份验证对象应用于 requests.PreparedRequest()
instance, 没有 .data
属性.
Session.request()
call(所有 request.<method>
和 session.<method>
调用都使用)的正常流程如下:
- 一个
Request()
实例使用与原始调用相同的所有参数创建 - 请求被传递给
Session.prepare_request()
,它首先将会话存储的基值与原始调用的参数合并,然后 - 创建了一个
PreparedRequest()
instance - 在准备好的请求实例上调用
PreparedRequest.prepare()
method,传入来自Request
实例和会话的合并数据。 prepare()
方法调用各种self.prepare_*
方法,包括PreparedRequest.prepare_auth()
.PreparedRequest.prepare_auth()
调用auth(self)
使身份验证对象有机会向请求附加信息。
不幸的是,在此流程中,除了 PreparedRequest.prepare()
和 PreparedRequest.prepare_body()
之外,其他任何人都无法使用原始 data
映射,并且在这些方法中,映射是局部变量。您无法从身份验证对象访问它。
那么您的选择是:
再次解码正文,并用更新后的映射调用
prepare_body()
。不使用身份验证对象,而是使用我回答中的其他路径;显式创建准备好的请求并首先操作
data
。使用 Python 堆栈玩快乐地狱,并从两帧以上的
prepare()
方法中提取局部变量。我真的不能推荐这条路。
为了更好地封装身份验证方法,我会进行解码/重新编码;后者通过重用 PreparedRequest.prepare_body()
:
import base64
import hashlib
import hmac
import time
try:
# Python 3
from urllib.parse import parse_qs
except ImportError:
# Python 2
from urlparse import parse_qs
from requests import AuthBase
URL_ENCODED = 'application/x-www-form-urlencoded'
class KrakenAuth(AuthBase):
"""a requests-module-compatible auth module for kraken.com"""
def __init__(self, key, secret):
self.api_key = key
self.secret_key = secret
def __call__(self, request):
ctheader = request.headers.get('Content-Type')
assert (
request.method == 'POST' and (
ctheader == URL_ENCODED or
requests.headers.get('Content-Length') == '0'
)
), "Must be a POST request using form data, or empty"
# insert the nonce in the encoded body
data = parse_qs(request.body)
data['nonce'] = nonce
request.prepare_body(data, None, None)
body = request.body
if not isinstance(body, bytes): # Python 3
body = body.encode('latin1') # standard encoding for HTTP
message = request.path_url + hashlib.sha256(b'%s%s' % (nonce, body)).digest()
hmac_key = base64.b64decode(self.secret_key)
signature = hmac.new(hmac_key, message, hashlib.sha512).digest()
signature = base64.b64encode(signature)
request.headers.update({
'API-Key': self.api_key,
'API-Sign': signature
})
return request