最近更新时间:2019-08-09 18:03:03
发送给分布式事务服务KDTX的HTTP请求中,必须包含授权参数和其他公共参数。分布式事务服务使用用户的Access Key ID和Secret Access Key进行加密方式来验证请求者身份。Access Key ID和Secret Access Key由金山云发给用户,Access Key ID作为用户的身份标识,Secret Access Key作为用户和服务器短进行签名计算的秘钥。
Http请求header中Authorization字段是服务的授权参数,其格式为:
Authorization="[HashMethod][空格]Credential=[access_key]/[scope],SignedHeaders=[signed_headers],Signature=[signature]"
其中:
[HashMethod] ="KSC4-HMAC-SHA256"
[access_key] =用户Access key ID
[scope] = [timestamp]/[region]/[service]/[req_type]
timestamp为yyyyMMdd格式的时间戳,region为请求服务所在区域名,service为访问的服务名,req_type为请求的类型。
[signed_headers]:将Headers按照name升序排列
[signed_headers] = [header_name_1];[header_name_2]....
签名算法:
[signature]= sha256(sha256(sha256(sha256(sha256("KSC4"+sign_key,timestamp),region),service),req_type),string_to_sign)
其中:
[sign_key] = 用户Secret Access Key
[stringToSign] = "KSC4-HMAC-SHA256" + "\n" + [X-Ksc-Date] + "\n" +[scope] + “\n” + SHA-256([canonical_request])
[canonical_request] = [HTTPRequestMethod] + "\n" + [CanonicalURI] + "\n" + [CanonicalQueryString] + "\n" + [CanonicalHeaders] + "\n" + [signed_headers] + "\n" + SHA-256([request_body])
[HTTPRequestMethod] = POST或GET
[CanonicalURI] = 请求URL中除去Endpoint之外的剩余部分。目前URL等于Endpoint,所以CanonicalURI为空
[CanonicalQueryString] = 空
[CanonicalHeaders]:按照[signed_headers]中的排序方式进行排序
[CanonicalHeaders] =
LowerCase (HeaderName1) + ‘:’ + Trim (HeaderValue1) + "\n"+LowerCase (HeaderName2) + ‘:’ + Trim (HeaderValue2) + "\n"+.......
[request_body] = Post 请求的body部分
import hashlib
import hmac
import urllib
import datetime
import requests
class RequestComposer:
def __init__(self, ak, sk, service, request_parameters, method, content_hash=False):
self.ak = ak
self.sk = sk
self.service = service
self.content_hash = content_hash
self.header_name_authorization = 'Authorization'
self.header_name_host = 'Host'
self.header_name_content_sha256 = 'X-Amz-Content-Sha256'
self.header_name_date = 'X-Amz-Date'
self.hash_keyword = 'AWS4'
self.hash_method = 'AWS4-HMAC-SHA256'
self.request_parameters = request_parameters
self.method = method
@staticmethod
def get_canonical_headers(headers):
canonical = []
for header in headers:
c_name = header.lower().strip()
raw_value = str(headers[header])
if '"' in raw_value:
c_value = raw_value.strip()
else:
c_value = ' '.join(raw_value.strip().split())
canonical.append('%s:%s' % (c_name, c_value))
return '\n'.join(sorted(canonical)) + '\n'
@staticmethod
def get_signed_headers(headers):
l = ['%s' % n.lower().strip() for n in headers]
l = sorted(l)
return ';'.join(l)
def get_headers(self, host, region, payload, additional_signing_headers=None):
if additional_signing_headers is None:
additional_signing_headers = {}
if self.ak is None or self.sk is None:
return None
# Create a date for headers and the credential string
t = datetime.datetime.utcnow()
amz_date = t.strftime('%Y%m%dT%H%M%SZ')
date_stamp = t.strftime('%Y%m%d') # Date w/o time, used in credential scope
# ************* TASK 1: CREATE A CANONICAL REQUEST *************
# http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
# Step 1 is to define the verb (GET, POST, etc.)--already done.
method = self.method
# Step 2: Create canonical URI--the part of the URI from domain to query
# string (use '/' if no path)
canonical_uri = '/'
# Step 3: Create the canonical query string. In this example, request
# parameters are passed in the body of the request and the query string
# is blank.
canonical_querystring = self.request_parameters
# Step 4: Create the canonical headers. Header names and values
# must be trimmed and lowercase, and sorted in ASCII order.
# Note that there is a trailing \n.
headers_to_sign = {
self.header_name_host: host, # sign indispensable
self.header_name_date: amz_date, # sign indispensable
}
if self.content_hash:
headers_to_sign[self.header_name_content_sha256] = hashlib.sha256(payload).hexdigest()
# additional headers can be added to signing process
for h in additional_signing_headers:
if h not in headers_to_sign:
headers_to_sign[h] = additional_signing_headers[h]
canonical_headers = self.get_canonical_headers(headers_to_sign)
# Step 5: Create the list of signed headers. This lists the headers
# in the canonical_headers list, delimited with ";" and in alpha order.
# Note: The request can include any headers; canonical_headers and
# signed_headers include those that you want to be included in the
# hash of the request. "Host" and "x-amz-date" are always required.
# For DynamoDB, content-type and x-amz-target are also required.
signed_headers = self.get_signed_headers(headers_to_sign)
# Step 6: Create payload hash. In this example, the payload (body of
# the request) contains the request parameters.
payload_hash = hashlib.sha256(payload).hexdigest()
# Step 7: Combine elements to create create canonical request
canonical_request = method + '\n' + canonical_uri + '\n' + \
canonical_querystring + '\n' + canonical_headers + '\n' + \
signed_headers + '\n' + payload_hash
# ************* TASK 2: CREATE THE STRING TO SIGN*************
# Match the algorithm to the hashing algorithm you use, either SHA-1 or
# SHA-256 (recommended)
algorithm = self.hash_method
credential_scope = date_stamp + '/' + region + '/' + self.service + '/' + 'aws4_request'
string_to_sign = algorithm + '\n' + amz_date + '\n' + credential_scope + '\n' + hashlib.sha256(
canonical_request).hexdigest()
# ************* TASK 3: CALCULATE THE SIGNATURE *************
# Create the signing key using the function defined above.
signing_key = self.getSignatureKey(self.sk, date_stamp, region, self.service)
# Sign the string_to_sign using the signing_key
signature = hmac.new(signing_key, string_to_sign.encode('utf-8'), hashlib.sha256).hexdigest()
# ************* TASK 4: ADD SIGNING INFORMATION TO THE REQUEST *************
# Put the signature information in a header named Authorization.
authorization_header = algorithm + ' ' + 'Credential=' + self.ak + '/' + credential_scope + ', ' + \
'SignedHeaders=' + signed_headers + ', ' + 'Signature=' + signature
# For DynamoDB, the request can include any headers, but MUST include "host", "x-amz-date",
# "x-amz-target", "content-type", and "Authorization". Except for the authorization
# header, the headers must be included in the canonical_headers and signed_headers values, as
# noted earlier. Order here is not significant.
# # Python note: The 'host' header is added automatically by the Python 'requests' library.
headers = {}
for h in headers_to_sign:
headers[h] = headers_to_sign[h]
headers[self.header_name_authorization] = authorization_header
return headers
# Key derivation functions. See:
@staticmethod
def sign(key, msg):
return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()
def getSignatureKey(self, key, date_stamp, region_name, service_name):
kDate = self.sign((self.hash_keyword + key).encode('utf-8'), date_stamp)
kRegion = self.sign(kDate, region_name)
kService = self.sign(kRegion, service_name)
kSigning = self.sign(kService, 'aws4_request')
return kSigning
class Client(object):
def __init__(self, host, ak=None, sk=None, service=None, version=None, region=None):
self.service = service
self.host = host
self.ak = ak
self.sk = sk
self.region = region
self.version = version
def call(self, target, dict_request_parameters=None, headers=None):
# usiing kop authentication
if headers is None:
headers = {}
url = 'http://%s?' % self.host
if isinstance(dict_request_parameters, str):
dict_request_parameters = eval(dict_request_parameters)
dict_request_parameters['Version'] = self.version
request_parameters = "Action=%s&" % target + '&'.join(
["%s=%s" % (k, urllib.quote(str(dict_request_parameters[k]), ''))
for k in sorted(dict_request_parameters.keys())])
url += request_parameters
composer = RequestComposer(ak=self.ak, sk=self.sk, service=self.service,
request_parameters=request_parameters, method='GET')
headers = composer.get_headers(host=self.host, region=self.region,
payload="", additional_signing_headers=headers)
r = requests.get(url, headers=headers)
return r
AK = 'YOUR ACCESS KEY'
SK = 'YOUR SECRET KEY'
VERSION = "2016-07-01"
SERVICE = "krds"
REGION = "cn-beijing-6"
HOST = "krds.%s.api.ksyun.com" % REGION
client = Client(HOST, AK, SK, SERVICE, VERSION, REGION)
additional_headers = {'Accept': 'application/json'}
result = client.call("DescribeDBEngineVersions", {'Engine': 'MySQL', 'EngineVersion': '5.5'}, additional_headers)
print result.json()