京東雲 OpenAPI 簽名機制的 Python 實現

京東雲OpenAPI是將京東雲所有資源的管理能力通過API的方式提供出來,供京東雲用戶和合作伙伴使用。OpenAPI是京東雲控制檯的有效補充,方便用戶更靈活的控制自己的雲上資源。

本文檔通過step-by-step生成簽名以及完整HTTP請求,並將請求發送至京東公共OpenAPI並得到response。

京東雲:www.jdcloud.com
京東雲OpenAPI:docs.jdcloud.com/cn/?act=3
公共說明:docs.jdcloud.com/cn/common-declaration/api/introduction

前置條件

要使用某個產品線的OpenAPI,首先需要先通過產品文檔瞭解產品功能、計費等方面的信息。

在開始調用京東雲OpenAPI之前,需提前在京東雲用戶中心賬戶管理下的AccessKey管理頁面申請AccessKey和SecretKey密鑰對(簡稱AK/SK)。AK/SK信息請妥善保管,如果遺失可能會造成非法用戶使用此信息操作您在雲上的資源,給你造成數據和財產損失。AK/SK密鑰對允許啓用、禁用,啓用後可用其調用OpenAPI,禁用後不能用其調用OpenAPI。

京東雲OpenAPI使用Restful接口風格,要進行OpenAPI調用需要包含如下信息:請求協議,請求方式,請求地址,請求路徑,請求頭,請求參數,請求體。

爲了您的數據安全,建議務必使用https協議。

基本步驟

  1. 初始基本配置
  2. 生成初始請求header
  3. 生成標準請求串
  4. 生成待簽名字符串
  5. 生成簽名key
  6. 計算簽名值
  7. 更新請求頭
  8. 發送請求

請求籤名具體 Python 實現

1. 初始基本配置

# GET請求
method = 'GET'
# 地域
region = 'cn-north-1'
# 產品線
service = 'vm'
# vm產品線服務地址:{product}.jdcloud-api.com
full_host = 'vm.jdcloud-api.com'

# OpenAPI服務的地址和路徑格式一般爲(默認爲https):
# https://{product}.jdcloud-api.com/{API版本號}/regions/{地域ID}/{資源名稱}/{資源ID(可選)}/{子資源名稱(可選)}/{子資源ID(可選)}{:自定義動作(可選)}
url = 'https://vm.jdcloud-api.com/v1/regions/cn-north-1/instances/i-uvvtdzuxre'

# 規範請求路徑Path:/{apiVersion}/regions/{regionId}/instances/{instanceId}
canonical_uri = '/v1/regions/cn-north-1/instances/i-uvvtdzuxre'

# 本例中爲GET請求,body爲空(若爲PUT/POST/PATCH請求,則body爲parameter的JSON格式)
body = ''

2. 生成初始請求header

# 當前時間
now = datetime.datetime.utcfromtimestamp(time.time())

# 格式化字符串爲:'20180812T074253Z'
jdcloud_date = now.strftime('%Y%m%dT%H%M%SZ')

# 生成datestamp字符串:“20180812”,用於簽名
datestamp = now.strftime('%Y%m%d')

# 隨機生成的字符串:“58542f21-bda3-4736-9a08-da2339669e52”
nonce = str(uuid.uuid4())

# 增加請求header字段:
# Content-Type:請求數據格式爲JSON
# User-Agent:格式爲“JdcloudSdkPython/<SDK版本> <產品線>/<產品線revision>”
# 請求頭如下:
#         {'x-jdcloud-nonce': '63e0148e-0fef-4c78-9228-47fefe470b07', 
#         'Content-Type': 'application/json', 
#         'x-jdcloud-date': '20180812T094103Z', 
#         'User-Agent': 'JdcloudSdkPython/1.2.1 vm/1.0.0'}
headers = {'Content-Type': 'application/json', 'User-Agent': 'JdcloudSdkPython/1.2.1 vm/1.0.0'}
headers['x-jdcloud-date'] = jdcloud_date
headers['x-jdcloud-nonce'] = nonce

3. 生成標準請求串

# 生成請求查詢字符串,本例爲空。
canonical_querystring = ''

# 生成CanonicalHeaders字符串,CanonicalHeaders爲需要參與簽名的請求頭及值。要創建規範 HTTP header 列表,請將所有 HTTP header 名稱轉換爲小寫,並刪除前導空格和尾隨空格。通過用字符代碼排序HTTP header ,然後遍歷 HTTP header 名稱來構建規範 HTTP header 列表。使用:分隔名稱和值,並添加換行符。
# 如下:
#         content-type:application/json
#         host:vm.jdcloud-api.com
#         x-jdcloud-date:20180812T074253Z
#         x-jdcloud-nonce:58542f21-bda3-4736-9a08-da2339669e52
#
canonical_headers = 'content-type' + ':' + headers['Content-Type'] + '\n' + 'host' + ':' + full_host + '\n' + 'x-jdcloud-date' + ':' + headers['x-jdcloud-date'] + '\n' + 'x-jdcloud-nonce' + ':' + headers['x-jdcloud-nonce'] + '\n'

# SignedHeaders用於告知京東雲,請求中的哪些頭是簽名過程的一部分,京東雲可以忽略哪些頭(例如,由代理添加的任何附加標頭),以驗證請求。注意host, x-jdcloud-date, x-jdcloud-nonce必須包含在內。
signed_headers = 'content-type;host;x-jdcloud-date;x-jdcloud-nonce'

# 生成body的hash(即使body爲空字符串): 
#        “e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855”
payload_hash = hashlib.sha256(body.encode('utf-8')).hexdigest()

# 生成標準請求串:
# GET
# /v1/regions/cn-north-1/instances/i-uvvtdzuxre
# 
# content-type:application/json
# host:vm.jdcloud-api.com
# x-jdcloud-date:20180812T074253Z
# x-jdcloud-nonce:58542f21-bda3-4736-9a08-da2339669e52
# content-type;host;x-jdcloud-date;x-jdcloud-nonce
#
# e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
canonical_request = (method + '\n' +
                     canonical_uri + '\n' +
                     canonical_querystring + '\n' +
                     canonical_headers + '\n' +
                     signed_headers + '\n' +
                     payload_hash)

4. 生成待簽名字符串

# 用於創建請求籤名的哈希算法,目前只支持 `JDCLOUD2-HMAC-SHA256`
algorithm = 'JDCLOUD2-HMAC-SHA256'

# CredentialScope格式爲”{時間}/{地域編碼}/{產品線}/jdcloud2_request\n”,例如20180130/cn-north-1/vpc/jdcloud2_request\n
# "20180812/cn-north-1/vm/jdcloud2_request"
credential_scope = (datestamp + '/' + region + '/' + service + '/' + 'jdcloud2_request')

# 生成canonical_request的哈希值
#         a3349e006302711650240165d89bb9c0b504ff8dc95d665cd98907877fbbd423
canonical_request_hash = hashlib.sha256(canonical_request.encode('utf-8')).hexdigest()

# 生成待簽名字符串:
#  StringToSign =
#         Algorithm + \n +
#         RequestDateTime + \n +
#         CredentialScope + \n +
#         HashedCanonicalRequest
# 結果爲:
# JDCLOUD2-HMAC-SHA256
# 20180812T074253Z
# 20180812/cn-north-1/vm/jdcloud2_request
# a3349e006302711650240165d89bb9c0b504ff8dc95d665cd98907877fbbd423

string_to_sign = algorithm + '\n' + jdcloud_date + '\n' + credential_scope + '\n' + canonical_request_hash

5. 生成簽名key

# 計算簽名key(二進制),其中HMAC(key, data)代表以二進制格式返回輸出的HMAC-SHA256函數
# 僞代碼:
# kSecret = your secret access key
# kDate = HMAC("JDCLOUD2" + kSecret, Date)
# kRegion = HMAC(kDate, Region)
# kService = HMAC(kRegion, Service)
# kSigning = HMAC(kService, "jdcloud2_request")

k_date = hmac.new(('JDCLOUD2' + secret_key).encode('utf-8'), datestamp.encode('utf-8'), hashlib.sha256).digest()

k_region = hmac.new(k_date, region.encode('utf-8'), hashlib.sha256).digest()

k_service = hmac.new(k_region, service.encode('utf-8'), hashlib.sha256).digest()

signing_key = hmac.new(k_service, 'jdcloud2_request'.encode('utf-8'), hashlib.sha256).digest()

6. 計算簽名值

# 最終生成簽名:
#     如:9b2026198d3acbf99da395e23a994ed369a0d70f5b4a5d7567dd0caf3009656d
encoded = string_to_sign.encode('utf-8')
signature = hmac.new(signing_key, encoded, hashlib.sha256).hexdigest()

7. 更新請求頭

# 計算簽名後,需要將簽名的結果作爲Authorization請求頭將其添加到請求中
# 'JDCLOUD2-HMAC-SHA256 Credential=xxxxxxxxxxxxxxxxxxxxxxxxxxx/20180812/cn-north-1/vm/jdcloud2_request, SignedHeaders=content-type;host;x-jdcloud-date;x-jdcloud-nonce, Signature=53305ed8290a26493beec3060d9b1ff7d94cb1a6f2171cd193a1562814c8de37'
authorization_header = algorithm + ' ' + 'Credential=' + access_key + '/' + credential_scope + ', ' + 'SignedHeaders=' + signed_headers + ', ' + 'Signature=' + signature

# 更新請求headers,如下:
#        {'x-jdcloud-date': '20180812T074253Z', 
#        'x-jdcloud-content-sha256': 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', 
#        'JDCLOUD2-HMAC-SHA256': 'JDCLOUD2-HMAC-SHA256', 
#        'x-jdcloud-nonce': '58542f21-bda3-4736-9a08-da2339669e52', 
#        'User-Agent': 'JdcloudSdkPython/1.2.1 vm/1.0.0', 
#        'Content-Type': 'application/json', 
#        'Authorization': 'JDCLOUD2-HMAC-SHA256 Credential=xxxxxxxxxxxxxxxxxxxxxxxxxxx/20180812/cn-north-1/vm/jdcloud2_request, SignedHeaders=content-type;host;x-jdcloud-date;x-jdcloud-nonce, Signature=53305ed8290a26493beec3060d9b1ff7d94cb1a6f2171cd193a1562814c8de37'}

headers['Authorization'] = authorization_header
headers['x-jdcloud-date'] = jdcloud_date
headers['x-jdcloud-content-sha256'] = payload_hash
headers['JDCLOUD2-HMAC-SHA256'] = 'JDCLOUD2-HMAC-SHA256'
headers['x-jdcloud-nonce'] = nonce

8. 發送請求

8.1 Python 發送請求

# Python發送請求,並獲得相應
resp = requests.request(method, url, data=body, headers=headers)

8.2 Curl發送請求

請求頭中必須包含SignedHeaders中的各個字段:

# curl -X GET -H "x-jdcloud-date:20180812T094103Z" -H "x-jdcloud-nonce:63e0148e-0fef-4c78-9228-47fefe470b07" -H "Content-Type:application/json" -H "Authorization:JDCLOUD2-HMAC-SHA256 Credential=xxxxxxxxxxxxxxxxxxxxxxxxxxx/20180812/cn-north-1/vm/jdcloud2_request, SignedHeaders=content-type;host;x-jdcloud-date;x-jdcloud-nonce, Signature=53305ed8290a26493beec3060d9b1ff7d94cb1a6f2171cd193a1562814c8de37" https://vm.jdcloud-api.com/v1/regions/cn-north-1/instances/i-uvvtdzuxre

總結

可將發送OpenAPI請求簡單總結爲:

  1. 生成待簽名字符串(包含header各個字段、body的hash等)
  2. 生成簽名key(包含SK並多層加鹽);
  3. 用key簽名step 1的待簽名字符串,將簽名信息加入到header併發送請求(包含AK),服務端按照同樣的方式可以校驗身份。

使用Python SDK發送請求源代碼:

from jdcloud_sdk.core.credential import *
from jdcloud_sdk.services.vm.client.VmClient import *
from jdcloud_sdk.services.vm.apis.DescribeInstanceRequest import *
import json
# 用戶AK&SK
access_key = '<your ak>'
secret_key = '<your sk>'
# 地域
regionId = 'cn-north-1'
# 要查詢的實例ID
instanceId = 'i-uvvtdzuxre'
# 生成Credential和Client
myCredential = Credential(access_key, secret_key)
myClient = VmClient(myCredential)
# 定義參數
myParam = DescribeInstanceParameters(regionId, instanceId)
# 定義請求
myRequest = DescribeInstanceRequest(myParam)
# Client發送請求,並得到響應
resp = myClient.send(myRequest)
# 將返回結果以JSON格式打印
print json.dumps(resp.result, indent=2)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章