在調用阿里雲 API 的時候,最讓人頭疼的就是 API 的簽名(Signature)機制,阿里雲在通用文檔中也有專項說明,但是僅僅有基於 Java 的實現代碼示例。所以這里基於 Python 來分析下。
基本步驟
- 構造規范化的請求字符串 (Canonicalized Query String)
- 構造被簽名字符串 StringToSign
- 計算 HMAC 值
- 計算簽名值
- 添加簽名
理論部分的詳細內容參考阿里雲官方幫助文檔吧
具體 Python 實現
API 請求原理
簡單來說調用阿里雲 API 就是一個 http 請求(大多數為 GET, 這里也是基於 GET 請求),只是后面要跟一堆參數,例如一個查看快照的請求為:
http://ecs.aliyuncs.com/?SignatureVersion=1.0&Format=JSON&Timestamp=2017-08-07T05%3A50%3A57Z&RegionId=cn-hongkong&AccessKeyId=xxxxxxxxx&SignatureMethod=HMAC-SHA1&Version=2014-05-26&Signature=%2FeGgFfxxxxxtZ2w1FLt8%3D&Action=DescribeSnapshots&SignatureNonce=b5046ef2-7b2b-11e7-a3c5-00163e001831&ZoneId=cn-hongkong-b
請求中所需要的公共參數(就是調用 API 都需要用到的參數)為:
SignatureVersion # 簽名算法版本,目前為 1.0 Format # 返回消息的格式化方式,JSON or XML 默認值為 XML Timestamp # 請求的時間戳,UTC時間,例如: 2013-01-10T12:00:00Z AccessKeyId # 賬號密鑰 ID SignatureMethod # 簽名方式,目前為 HMAC-SHA1 Version # 版本號,為日期形式,例如: 2014-05-26 每個產品不同 Signature # 最難搞定的簽名 SignatureNonce # 唯一隨機數,防止網絡攻擊。不同請求間使用不同的隨機數。
除了 Signature 之外,其它的參數都比較容易獲得,有些甚至是固定的值,具體可以參考阿里雲文檔
除了公共參數之外,還需要具體接口(Action)的請求參數,每個 Action 接口的參數可以參考對應產品的接口文檔,例如 DescribeLoadBalancers
而 Signature 是基於公共參數和接口參數的,所以比較復雜。
Signature 具體代碼實現
構造規范化的請求字符串 (Canonicalized Query String)
- 構造 dict
Python 中體現參數一一對應的就是 dict, 創建一個 dict, 把請求參數些進去,這里簡化參數只有這些:
>>> D = { 'Format':'JSON', 'Version':'2014-05-26', 'SignatureMethod':'HMAC-SHA1' }
- 排序
由於簽名要求唯一性,包括順序,所以需要按照參數名稱排序
>>> sortedD = sorted(D.items(), key=lambda x: x[0]) >>> sortedD [('Format', 'JSON'), ('SignatureMethod', 'HMAC-SHA1'), ('Version', '2014-05-26')] # 先通過 D.items() 轉化為 List, 然后利用 sorted 方式按照 key 排序
- URL 編碼
由於在標准請求字符串中需要使用 UTF-8 字符集,對請求參數名稱和值中有些不符合規范的字符要進行 url 編碼,具體規則為:
字符 AZ、az、0~9 以及字符“-”、“_”、“.”、“~”不編碼;
其它字符編碼成 %XY 的格式,其中 XY 是字符對應 ASCII 碼的 16 進制表示。比如英文的雙引號(”)對應的編碼為 %22;
對於擴展的 UTF-8 字符,編碼成 %XY%ZA… 的格式;
英文空格( )要編碼成 %20,而不是加號(+)。
注意:一般支持URL編碼的庫(比如 Java 中的 java.net.URLEncoder)都是按照 “application/x-www-form-urlencoded”的 MIME 類型的規則進行編碼的。實現時可以直接使用這類方式進行編碼,把編碼后的字符串中加號(+)替換成 %20、星號(*)替換成 %2A、%7E 替換回波浪號(~),即可得到上述規則描述的編碼字符串。
這里使用 python 中的 urllib 庫來進行編碼:
>>> def percentEncode(str): res = urllib.quote(str.decode(sys.stdin.encoding).encode('utf8'), '') res = res.replace('+', '%20') res = res.replace('*', '%2A') res = res.replace('%7E', '~') return res # 這里構造一個編碼函數,對一個字符串進行編碼,返回編碼后的字符串
- 生成標准化請求字符串
>>> canstring = '' >>> for k,v in sortedD: canstring += '&' + percentEncode(k) + '=' + percentEncode(v) >>> canstring '&Format=JSON&SignatureMethod=HMAC-SHA1&Version=2014-05-26'
構造被簽名字符串 StringToSign
規則為:
StringToSign=
HTTPMethod + “&” +
percentEncode(“/”) + ”&” +
percentEncode(CanonicalizedQueryString)
所以在這個實例中
>>> stringToSign = 'GET&%2F&' + percentEncode(canstring[1:]) >>> stringToSign 'GET&%2F&Format%3DJSON%26SignatureMethod%3DHMAC-SHA1%26Version%3D2014-05-26' # >>> percentEncode(“/”) # %2F
計算 HMAC 值
>>> access_key_secret = 'access_key_secret' >>> h = hmac.new(access_key_secret + "&", stringToSign, sha1) >>> h <hmac.HMAC instance at 0x35ed440> # access_key_secret 是通過阿里雲賬號中的 AK 中獲取的,和 access_key_id 對應,測試的時候使用的是 'access_key_secret'
計算簽名值
>>> signature = base64.encodestring(h.digest()).strip() >>> signature 'sq8LVH+ZItZiVQ0/rVnHV1kP/BE='
到此生成了 signature 簽名
添加簽名
>>> D['Signature'] = signature >>> D {'Format': 'JSON', 'Signature': 'sq8LVH+ZItZiVQ0/rVnHV1kP/BE=', 'SignatureMethod': 'HMAC-SHA1', 'Version': '2014-05-26'}
所以在這個實例中,最終請求的 url 為
>>> url = 'http://ecs.aliyuncs.com/?' + urllib.urlencode(D) >>> url 'http://ecs.aliyuncs.com/?SignatureMethod=HMAC-SHA1&Version=2014-05-26&Signature=sq8LVH%2BZItZiVQ0%2FrVnHV1kP%2FBE%3D&Format=JSON'
拿到瀏覽器直接訪問即可,得到結果為:
{"Message":"The input parameter \"Action\" that is mandatory for processing this request is not supplied.","RequestId":"129880D4-710D-4D2C-9F8B-12777FA1D3C6","HostId":"ecs.aliyuncs.com","Code":"MissingParameter"}
由於是測試環境,就給了三個參數,所以還少很多參數,正常來說把這些參數都加上,然后生成 signature,組成 url 后直接訪問就可以得到結果。
文檔 來自:https://www.jianshu.com/p/7574349a5042
