实为吾之愚见,望诸君酌之!闻过则喜,与君共勉
本篇主要是做下rds签名机制的补充,以简单的代码实例来解释下每个步骤的含义,尽量会按照官方文档的描述举例,关于签名机制的官方文档在这里:
一:先构造字符串
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
得到如下请求结果: