如何生成阿里云 OSS 访问控制签名

最近项目为降低运维成本,采用了阿里云的 OSS、RDS、SLB。其他服务对开发来讲已足够傻瓜,要点三十二个赞。相对来讲只有 OSS 还有点门槛,但没办法,存储业务使然。
我们通过 JS 来上传文件到 OSS,采用 PostObject。因为控制访问(只读或读写都限制的情况),Post 时需要带上 policy 和 Signature。但是这里官方文档有些歧义,没明确说明用于生成 Signature 的 policy 字符串是否是需要 base64 加密,也没有附带例子要自己试错,没人性啊就算是 5 分钟,用户的时间也是时间啊。PD 偷懒了啊,要知道文档写得好,老婆不会跑啊。云计算是一把手项目,看来此言非虚。
另外因为 RFC2045 规定了 Base64 一行最长 76 个字符(也有可能 64 个字就换行的哦),生成 Signature 时要注意把换行符去掉。
还有注意时区要用 GMT。

Java 实现

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public StorageAccessTokenDo generateStorageAccessToken(String fileName, Integer userId,
Integer expireSeconds) {
// 默认600秒后过期
if (null == expireSeconds || expireSeconds < 0) {
expireSeconds = 600;
}
// 获得GMT时间
TimeZone defaultTimeZone = TimeZone.getDefault();
TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT");
TimeZone.setDefault(gmtTimeZone);
Calendar calendar = Calendar.getInstance(gmtTimeZone);
calendar.add(Calendar.SECOND, expireSeconds);
Date expire = calendar.getTime();
TimeZone.setDefault(defaultTimeZone);
// 开始组装policy
String expiration = DateUtils.formatDateTime(DateUtils.nextSecond(expire),
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
String policyOrigin = "{\"expiration\": \"" + expiration
+ "\", \"conditions\": [{\"bucket\": \"" + bucketName + "\"}]}";
String policy = base64Encode(policyOrigin.getBytes());
// 生成签名
String signature = getSignature(policy.getBytes(), accessKeySecret.getBytes());
// 生成key(含目录路径)
String key = DateUtils.formatDateDirectory(new Date()) + userId + "/" + fileName;
// 返回storageAccessToken
StorageAccessTokenDo sat = new StorageAccessTokenDo(accessKeyId, policy, signature, key);
return sat;
}
/**
* base64加密
*
* @param origin
* @return
*/
@SuppressWarnings("restriction")
private static String base64Encode(byte[] origin) {
if (null == origin) {
return null;
}
return new sun.misc.BASE64Encoder().encode(origin).replace("\n", "").replace("\r", "");
}
/**
* 使用HmacSHA1加密方式生成签名数据
*
* @param data
* @param key
* @return
* @throws InvalidKeyException
* @throws NoSuchAlgorithmException
*/
private static String getSignature(byte[] data, byte[] key) {
SecretKeySpec signingKey = new SecretKeySpec(key, HMAC_SHA1);
try {
Mac mac;
mac = Mac.getInstance(HMAC_SHA1);
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(data);
return base64Encode(rawHmac);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
return null;
}

Python2 实现

1
2
3
4
5
6
7
8
9
10
11
import base64
import hmac
import sha
accessKeySecret = "NmgYoieU2AC5hnI4tsQq2zRgZvx2Lq"
policy = '''{"expiration": "2015-07-01T12:00:00.000Z", "conditions": [{"bucket": "test-bucket"}]}'''
print('policy = ' + base64.encodestring(policy)) #注意,76 个字符后会换行
signature = hmac.new(accessKeySecret, base64.encodestring(policy).strip().replace("\n", ""), sha)
print('signature = ' + base64.encodestring(signature.digest()).strip())