rds 簽名機制例項及補充說明
實為吾之愚見 , 望諸君酌之 ! 聞過則喜,與君共勉
本篇主要是做下rds簽名機制的補充,以簡單的程式碼例項來解釋下每個步驟的含義,儘量會按照官方文件的描述舉例,關於簽名機制的官方文件在這裡:
QQp" rel="nofollow,noindex" target="_blank">https://help.aliyun.com/document_detail/26225.html?spm=5176.11065259.1996646101.searchclickresult.5f0d113dsmRQQp
一:先構造字串
1.1 按照引數名稱的 字典順序 對請求中所有的請求引數(包括文件中描述的 “ 公共請求引數 ” 和給定了的請求介面的自定義引數,但不能包括 “ 公共請求引數 ” 中提到 Signature 引數本身) 進行排序 。
例子:以 DescribeDBInstances 舉例,該 api 需要的引數如下: SignatureVersion 、 SignatureNonce 、 SignatureMethod 、 Timestamp 、 Version 、 Action 、 AccessKeyId 、 Format 、 RegionId
按照要求,以字典順序進行排序,結果如下:
[AccessKeyId, Action, Format, RegionId, SignatureMethod, SignatureNonce, SignatureVersion, Timestamp, Version]
這個順序,就是在使用GET方法提交請求時的引數順序,這些引數就是請求URL中的引數部分(即URL中“?”之後由“&”連線的部分)。
比如使用下面的方法排序:
String[] sortStr1 = new String[]{ "Action" , "AccessKeyId" , "RegionId" , "SignatureVersion" , "SignatureNonce" , "SignatureMethod" , "Timestamp" , "Version" , "Format" };
Arrays. sort ( sortStr1 );
System . out .println(Arrays. toString ( sortStr1 ));
1.2 對 每個請求引數 的 名稱和值 進行 編碼 。名稱和值要使用 UTF-8 字符集進行URL編碼,URL編碼的編碼規則是:
對於字元 A-Z、a-z、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替換回波浪號(~),即可得到上述規則描述的編碼字串。
例子:之前已經把引數做了排序,第二步是吧引數的名稱和值使用utf-8字符集進行編碼,按照說明,可以” 直接使用 這類方式進行編碼,把編碼後的字串中加號(+)替換成%20、星號(*)替換成%2A、%7E替換回波浪號(~),即可得到上述規則描述的編碼字串”,,名稱和值分別如下:
AccessKeyId "LTAI0CeFaZcIg5cV"
Action "DescribeDBInstances"
Format "XML" ;
RegionId "cn-beijing" ;
SignatureVersion "1.0" ;
SignatureNonce UUID. randomUUID ().toString();
SignatureMethod "HMAC-SHA1" ;
Timestamp Instant. now ().toString().substring(0, 19);
Version "2014-08-15" ;
編碼方式是 :
java.net.URLEncoder.encode(key,"utf-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~")
則名稱是:
java.net.URLEncoder.encode(“ AccessKeyId ”,"utf-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~")
java.net.URLEncoder.encode(“ Action ”,"utf-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~")
…
…
…
java.net.URLEncoder.encode(“ Version ”,"utf-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~")
值是:
java.net.URLEncoder.encode(“ LTAI0CeFaZcIg5cV ”,"utf-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~")
java.net.URLEncoder.encode(“ DescribeDBInstances ”,"utf-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~")
…
…
…
java.net.URLEncoder.encode(“ 2014-08-15 ”,"utf-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~")
最終編碼完的名稱和值如下:
AccessKeyId LTAI0CeFaZcIg5cV
Action DescribeDBInstances
Format XML
RegionId cn-beijing
SignatureMethod HMAC-SHA1
SignatureNonce 14d01fb6-0c62-48ae-b3f0-2b6f2b3c9428
SignatureVersion 1.0
Timestamp 2018-09-19T16%3A46%3A05
Version 2014-08-15
1.3 對編碼後的引數名稱和值使用英文等號( = )進行連線。
例子 : 根據之前編碼後的名稱和值,用 = 連線後如下:
AccessKeyId= LTAI0CeFaZcIg5cV
Action=DescribeDBInstances
Format=XML
RegionId=cn-beijing
SignatureMethod=HMAC-SHA1
SignatureNonce= 14d01fb6-0c62-48ae-b3f0-2b6f2b3c9428
SignatureVersion=1.0
Timestamp= 2018-09-19T16%3A46%3A05
Version=2014-08-15
1.4 再把英文等號連線得到的字串按引數名稱的字典順序依次使用 & 符號連線,即得到規範化請求字串
例子 : 按照第一步得到的引數順序,用 & 拼接
AccessKeyId= LTAI0CeFaZcIg5cV &Action=DescribeDBInstances&Format=XML&RegionId=cn-beijing&SignatureMethod=HMAC-SHA1&SignatureNonce= 14d01fb6-0c62-48ae-b3f0-2b6f2b3c9428&SignatureVersion=1.0& Timestamp=2018-09-19T16%3A46%3A05& Version=2014-08-15
二, 構造用於計算簽名的字串
使用上一步構造的規範化字串按照下面的規則構造用於計算簽名的字串:
StringToSign=
HTTPMethod + "&" +
percentEncode ( "/" ) + " & " +
percentEncode (CanonicalizedQueryString)
引數說明:
o
§ HTTPMethod :提交請求用的 HTTP 方法,比 GET 。
§ percentEncode(“/“) :按照 1.b 中描述的 URL 編碼規則對字元 “/” 進行編碼得到的值,即 “%2F” 。
§ percentEncode(CanonicalizedQueryString) :對第 1 步中構造的規範化請求字串按步驟 1.ii 中描述的 URL 編碼規則編碼後得到的字串。
例子 :
按照描述, StringToSign 由這幾部分組成,分別是 HTTPMethod 、 & 、 percentEncode(“/“) 、 & 、 percentEncode(CanonicalizedQueryString) ,則這裡:
HTTPMethod : 使用 get
& :保持不變
percentEncode ( "/" ) :java.net.URLEncoder.encode("/","utf-8"), 即 %2F
percentEncode(CanonicalizedQueryString) :
java.net.URLEncoder.encode("AccessKeyId= LTAI0CeFaZcIg5cV &Action=DescribeDBInstances&Format=XML&RegionId=cn-beijing&SignatureMethod=HMAC-SHA1&SignatureNonce= 14d01fb6-0c62-48ae-b3f0-2b6f2b3c9428&SignatureVersion=1.0&Timestamp= 2018-09-19T16%3A46%3A05&Version=2014-08-15","utf-8"), 即 :
AccessKeyId=LTAI0CeFaZcIg5cV&Action=DescribeDBInstances&Format=XML&RegionId=cn-beijing&SignatureMethod=HMAC-SHA1&SignatureNonce=14d01fb6-0c62-48ae-b3f0-2b6f2b3c9428&SignatureVersion=1.0&Timestamp=2018-09-19T16%3A46%3A05&Version=2014-08-15
最終,得到的 StringToSign 是:
GET&%2F&AccessKeyId%3DLTAI0CeFaZcIg5cV%26Action%3DDescribeDBInstances%26Format%3DXML%26RegionId%3Dcn-beijing%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3D14d01fb6-0c62-48ae-b3f0-2b6f2b3c9428%26SignatureVersion%3D1.0%26Timestamp%3D2018-09-19T16%253A46%253A05%26Version%3D2014-08-15
- 注 : 以上的步驟,可以寫到一起來得出,比如下面的程式碼:
String Action="DescribeDBInstances";
String AccessKeyId="LTAI0CeFaZcIg5cV";
String Format="XML";
String RegionId="cn-beijing";
String SignatureVersion="1.0";
String SignatureNonce=UUID.randomUUID().toString();
String SignatureMethod="HMAC-SHA1";
String Timestamp=Instant.now().toString().substring(0, 19);
String Version="2014-08-15";
TreeMap<String, String> treeMap1 = new TreeMap<String, String>();
treeMap1.put("Action", Action);
treeMap1.put("AccessKeyId", AccessKeyId);
treeMap1.put("Format", Format);
treeMap1.put("RegionId", RegionId);
treeMap1.put("SignatureVersion", SignatureVersion);
treeMap1.put("SignatureNonce", SignatureNonce);
treeMap1.put("SignatureMethod", SignatureMethod);
treeMap1.put("Timestamp", Timestamp);
treeMap1.put("Version", Version);
Iterator iter=treeMap1.entrySet().iterator();
StringBuffer sb = new StringBuffer();
while(iter.hasNext()){
//System.out.println(iter.hasNext());
@SuppressWarnings("rawtypes")
Map.Entry entry=(Map.Entry) iter.next();
String key=entry.getKey().toString();
String value=entry.getValue().toString();
System.out.println(java.net.URLEncoder.encode(key,"utf-8"));
System.out.println(java.net.URLEncoder.encode(key,"utf-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~")+"="+java.net.URLEncoder.encode(value,"utf-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~"));
sb.append(java.net.URLEncoder.encode(key,"utf-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~")+"="+java.net.URLEncoder.encode(value,"utf-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~")+"&");
}
String sts=java.net.URLEncoder.encode(sb.toString().substring(0, 238));
String StringToSign="GET"+"&"+"%2F"+"&"+sts;
三,計算hmac值
按照 RFC2104 的定義,使用上面的用於簽名的字串計算簽名 HMAC 值。
注意計算簽名時使用的 Key 就是使用者持有的 Access Key Secret 並加上一個“ & ”字元 (ASCII:38) ,使用的雜湊演算法是 SHA1 。
例子: hmac-sha1 的演算法程式碼實現可以網上搜索,然後計算,以下為事例:
HmacSha1Signature hmac=new HmacSha1Signature();
byte[] Signature =hmac.calculateRFC2104HMAC( StringToSign ," lpc2nHx6OUBbTlG7TviOc12XnWf9gO&");
注意, Access Key Secret 後面要加一個 &
四,計算簽名值
按照 Base64 編碼規則把上面的 HMAC 值編碼成字串,即得到簽名值( Signature )。
例子 :
String Signature_ Base64 = Base64.encodeBase64String( Signature );
得到:
DJG/5KS60WAHbhGuRPR60WH/2BQ=
五,編碼簽名
將得到的簽名值作為 Signature 引數新增到請求引數中,即完成對請求籤名的過程。
說明 : 得到的簽名值在作為 最後 的請求引數值提交給 RDS 伺服器的時候,要和其他引數一樣, 按照 RFC3986 的規則進行 URL 編碼)。
例子 : 第四步得到的簽名是 jVBrEWqwk61+1N7n8BF7uszqDiU= ,則引數就是: Signature= DJG/5KS60WAHbhGuRPR60WH/2BQ=
編碼後是 : Signature=DJG%2F5KS60WAHbhGuRPR60WH%2F2BQ%3D
至此簽名已經拿到,則新增到請求 url :
http://rds.aliyuncs.com/?AccessKeyId=LTAI0CeFaZcIg5cV&Action=DescribeDBInstances&Format=XML&RegionId=cn-beijing&SignatureMethod=HMAC-SHA1&SignatureNonce=14d01fb6-0c62-48ae-b3f0-2b6f2b3c9428&SignatureVersion=1.0&Timestamp=2018-09-19T16%3A46%3A05&Version=2014-08-15&&Signature=DJG%2F5KS60WAHbhGuRPR60WH%2F2BQ%3D
得到如下請求結果: