背景
调用各大平台的API都需要签名,所以调用AWS的API自然也是一样.原本我想的非常简单并且觉得网上的资料和示例应该一抓一大把.但是事与愿违,如果直接使用平台的提供的SDK或者CLI的话,的确是非常简单,开箱即用.但是几乎没有完全从头开始实现签名的,就连官方文档也是讲的不是特别清楚.所以我准备自己来把自己的实现过程记录下来.
准备
我觉得网上资料不清晰的很重要的一个问题就是前置条件模糊.所以在开始之前我们先把一些需要用到的东西罗列一下,中心化的服务Region就填”us-east-1”.以下所有的计算过程都是基于这些配置
配置项 |
值 |
AccessKey |
AKIAWJY3VMZXCIL6BDSQ |
SecretKey |
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 |
Region |
us-east-1 |
Service |
iam |
URL |
https://iam.amazonaws.com/ |
X-Amz-Date |
20221229T082438Z |
过程中需要的几个函数也要标准化一下
1 2 3 4 5 6 7
| # sha256Hex 空字符串:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 test:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
# hmacSHA256Hex 密钥:test 值:111 结果:d37dffac8a59021d523b56de30c62f17642ea0d6e13c53c595da87fa3f7c0ccb
|
有了这些对齐的字段,后面的计算过程的每一步都是可以校验的
概览
AWS的签名过程总共分成四步:
- 创建规划的请求结构: 就是把HTTP请求按照一定的规划组合在一起
- 将第一步请求体做sha256Hex摘要加上一些特定字段组合签名目标字符串
- 按照一定规则派生出密钥,并对第二步的签名目标做签名
- 将签名结果加到HTTP Header中
Step1
先看一下HTTP请求体,拼接的规则可以查看官方文档
1 2 3 4
| GET /?Action=ListUsers&Version=2010-05-08 HTTP/1.1 Host: iam.amazonaws.com X-Amz-Date: 20221229T082438Z Authorization: AWS4-HMAC-SHA256 Credential=AKIAWJY3VMZXCIL6BDSQ/20221229/us-east-1/iam/aws4_request, SignedHeaders=host;x-amz-date, Signature=b1869a003e01966cc1a7667dde231aaaf89db6f2c614822fc7a14012e91e1596
|
拼接出来的字符串,最后面的payload的sha256Hex就是空字符串的sha256Hex
1 2 3 4 5 6 7 8
| GET / Action=ListUsers&Version=2010-05-08 host:iam.amazonaws.com x-amz-date:20221229T082438Z
host;x-amz-date e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| String datetime = "20221229T082438Z"; String httpMethod = "GET"; String url = "https://iam.amazonaws.com/"; String host = new URL(url).getHost(); Map<String, String> urlParams = new TreeMap<>(); urlParams.put("Action", "ListUsers"); urlParams.put("Version", "2010-05-08"); Map<String, String> headerMap = new TreeMap<>(); headerMap.put("X-Amz-Date", datetime); headerMap.put("Host", host); String body = null;
public static String buildCanonicalRequest(String httpMethod, String url, Map<String, String> urlParams, Map<String, String> headerMap, String body) throws Exception { StringBuilder sb = new StringBuilder(); sb.append(httpMethod); sb.append("\n"); sb.append(new URL(url).getPath()); sb.append("\n"); if (urlParams != null && !urlParams.isEmpty()) { for (Map.Entry<String, String> entry : urlParams.entrySet()) { sb.append(entry.getKey()); sb.append("="); sb.append(entry.getValue()); sb.append("&"); } sb.deleteCharAt(sb.lastIndexOf("&")); } sb.append("\n");
if (headerMap != null && !headerMap.isEmpty()) { String headerList = headerMap.keySet().stream().map(String::toLowerCase).collect(Collectors.joining(";")); for (Map.Entry<String, String> entry : headerMap.entrySet()) { sb.append(StringUtils.lowerCase(entry.getKey())); sb.append(":"); sb.append(entry.getValue()); sb.append("\n"); } sb.append("\n"); sb.append(headerList); sb.append("\n"); }
if (StringUtils.isBlank(body)) { body = ""; } sb.append(DigestUtils.sha256Hex(body));
return sb.toString(); }
|
Step2
构建签名目标字符串,对第二步的结果做sha256Hex
1 2 3 4 5 6 7 8 9 10 11 12
| GET / Action=ListUsers&Version=2010-05-08 host:iam.amazonaws.com x-amz-date:20221229T082438Z
host;x-amz-date e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ------------------------------------------------------------------------------ sha256Hex: f87889c26a9eafc1418c7291faab6fa0e1533eeebdeeb910390560da1f329304 ------------------------------------------------------------------------------
|
拼接成签名目标字符串
1 2 3 4
| AWS4-HMAC-SHA256 20221229T082438Z 20221229/us-east-1/iam/aws4_request f87889c26a9eafc1418c7291faab6fa0e1533eeebdeeb910390560da1f329304
|
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| String datetime = "20221229T082438Z";
String scope = "20221229/us-east-1/iam/aws4_request"; String requestHashHex = "f87889c26a9eafc1418c7291faab6fa0e1533eeebdeeb910390560da1f329304";
public static String buildStringToSign(String datetime, String scope, String requestHashHex) { StringBuilder sb = new StringBuilder(); sb.append("AWS4-HMAC-SHA256"); sb.append("\n"); sb.append(datetime); sb.append("\n"); sb.append(scope); sb.append("\n"); sb.append(requestHashHex); return sb.toString(); }
|
Step3
签名结果:
1 2 3 4 5 6
| AWS4-HMAC-SHA256 20221229T082438Z 20221229/us-east-1/iam/aws4_request f87889c26a9eafc1418c7291faab6fa0e1533eeebdeeb910390560da1f329304 ------------------------------------------------------------------- 签名结果: fb06ab6c15a164473a7c598df41e3735607f4992aedbd47a534214012ecb8ec2
|
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| String secretKey = "ZMXkC4V2PlF5mkmLQFb+FecxDXySOjET7pQwR3ZD"; String date = "20221229"; String region = "us-east-1"; String service = "iam"; String stringToSign = """ AWS4-HMAC-SHA256 20221229T082438Z 20221229/us-east-1/iam/aws4_request f87889c26a9eafc1418c7291faab6fa0e1533eeebdeeb910390560da1f329304 """;
public static String sign(String secretKey, String date, String region, String service, String stringToSign) throws Exception { byte[] kSecret = ("AWS4" + secretKey).getBytes("UTF8"); byte[] kDate = HmacUtils.hmacSHA256(kSecret, date); byte[] kRegion = HmacUtils.hmacSHA256(kDate, region); byte[] kService = HmacUtils.hmacSHA256(kRegion, service); byte[] kSigning = HmacUtils.hmacSHA256(kService, "aws4_request"); byte[] signByte = HmacUtils.hmacSHA256(kSigning, stringToSign); String sign = Hex.encodeHexString(signByte); return sign; }
|
Step4
将签名结果加到HTTP Header中:
1
| AWS4-HMAC-SHA256 Credential=AKIAWJY3VMZXCIL6BDSQ/20221229/us-east-1/iam/aws4_request,SignedHeaders=host;x-amz-date,Signature=fb06ab6c15a164473a7c598df41e3735607f4992aedbd47a534214012ecb8ec2
|
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| String accessKey = "AKIAWJY3VMZXCIL6BDSQ"; String scope = "20221229/us-east-1/iam/aws4_request"; String headerList = "host;x-amz-date"; String sign = "fb06ab6c15a164473a7c598df41e3735607f4992aedbd47a534214012ecb8ec2";
public static String buildAuthorization(String accessKey, String scope, String headerList, String sign) { StringBuilder authorizationBuilder = new StringBuilder(); authorizationBuilder.append("AWS4-HMAC-SHA256"); authorizationBuilder.append(" "); authorizationBuilder.append("Credential=" + accessKey); authorizationBuilder.append("/"); authorizationBuilder.append(scope); authorizationBuilder.append(","); authorizationBuilder.append("SignedHeaders="); authorizationBuilder.append(headerList); authorizationBuilder.append(","); authorizationBuilder.append("Signature="); authorizationBuilder.append(sign); return authorizationBuilder.toString(); }
|
最后
分享过程中几个用到的小工具:
完整代码在这里: https://gist.github.com/ichengchao/a08ff7052ea0f662b3e5c09d178e3e4c