亚马逊S3`POST`上传策略

前言

最近在写后端,需要后端生成token给前端直接上传文件到S3,减轻服务器压力,记录一下踩的坑,附上官方文档
https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/API/sigv4-UsingHTTPPOST.html

上传策略

第一步就是要创建上传策略,官方提供的模板是这样的,根据自己需要增加或减少策略属性,最终将这么一串策略进行base64编码一下得到StringToSign

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{ "expiration": "2015-12-30T12:00:00.000Z",
"conditions": [
{"bucket": "sigv4examplebucket"},
["starts-with", "$key", "user/user1/"],
{"acl": "public-read"},
{"success_action_redirect": "http://sigv4examplebucket.s3.amazonaws.com/successful_upload.html"},
["starts-with", "$Content-Type", "image/"],
{"x-amz-meta-uuid": "14365123651274"},
{"x-amz-server-side-encryption": "AES256"},
["starts-with", "$x-amz-meta-tag", ""],

{"x-amz-credential": "AKIAIOSFODNN7EXAMPLE/20151229/us-east-1/s3/aws4_request"},
{"x-amz-algorithm": "AWS4-HMAC-SHA256"},
{"x-amz-date": "20151229T000000Z" }
]
}

那好吧,这么一串json属性又多,格式又乱,那应该有提供生成器吧,结果看了半天api文档都没找到(可能是我眼花了,有找到的朋友告知一下谢谢),介绍文档也只是说需要怎么操作,并没有提供工具。那只好自己来生成,我的思路是在本地新建个policy.json文件,将我需要的最终策略的json形式复制在里面,然后代码读取动态替换掉value,类似这样:string.replaceAll("replace-bucket", bucket)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"expiration": "replace-expiration",
"conditions": [
{
"bucket": "replace-bucket"
},
[
"starts-with",
"$key",
"replace-dir"
],
{
"acl": "replace-acl"
}
}

最后base64一下就拿到StringToSign,后面签署签名的时候要用到

1
2
// BinaryUtils是amazon sdk提供的工具
String encodePolicy = BinaryUtils.toBase64(policy.getBytes("utf-8"));

签名key

创建完policy之后就是创建签名key了,这一步文档倒是说得比较明白

官方也有提供对应的HMAC-SHA256

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static byte[] HmacSHA256(String data, byte[] key) throws Exception {
String algorithm = "HmacSHA256";
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(key, algorithm));
return mac.doFinal(data.getBytes("utf-8"));
}

private static byte[] getSignatureKey(String key, String dateStamp, String regionName, String serviceName) throws Exception {
byte[] kSecret = ("AWS4" + key).getBytes("utf-8");
byte[] kDate = HmacSHA256(dateStamp, kSecret);
byte[] kRegion = HmacSHA256(regionName, kDate);
byte[] kService = HmacSHA256(serviceName, kRegion);
byte[] kSigning = HmacSHA256("aws4_request", kService);
return kSigning;
}

签名

最后一步就是拿着签名key签署StringToSign,还是用官方提供的HMAC-SHA256,最终得到的是byte[]类型的签名,需要转换成string类型

1
2
3
4
5
6
7
8
9
10
11
private static String byte2hex(byte[] b) {
StringBuilder hs = new StringBuilder();
String stmp;
for (int n = 0; b != null && n < b.length; n++) {
stmp = Integer.toHexString(b[n] & 0XFF);
if (stmp.length() == 1)
hs.append('0');
hs.append(stmp);
}
return hs.toString().toLowerCase();
}

  • 这里也是要注意签名的大小写,弄了半天大写一直说签名不对

签名例子

1
2
3
4
String policy = ...;
String encodePolicy = BinaryUtils.toBase64(policy.getBytes("utf-8"));
byte[] bytes = getSignatureKey(accessKeySecret, "20180606", "ap-southeast-1", "s3");
String signature = byte2hex(HmacSHA256(encodePolicy, bytes));

返回数据

根据上面三步拿到两个POST上传需要的东西encode-policysignature,然后将其他策略条件一并返回给前端,让前端直接将文件上传到s3