业务场景
由于项目中未来会产生大批量的音视频文件,全部存在服务器本地将会占用大量的空间;因此决定使用阿里云对象存储服务(Object Stroage Service, OSS)实现文件上云;本地只保存最近3个月的文件,过期文件将会被删除。如果要查看过期文件,则先从OSS中下载该文件,然后再进行其他的常规操作。
因为是第一次使用OSS,因此前期做了大量的准备工作。在申请了公司的OSS账号之后,使用常规的上传下载会提示没有权限,因此在获取OSS连接时,需要加一个参数SecurityToken。而在我获取SecurityToken的时候,发现直接使用SDK中的AssumeRole请求是无法获取的,这个原因是因为开发是在内网环境,而OSS的操作只能在外网环境进行访问,因此还需要通过Proxy代理从内网通过代理服务器访问外网。通过之前获取SecurityToken时的报错信息知道其访问域名是sts.aliyuncs.com;在官方文档中找到了公司对应使用的endPoint的STS地址,详见阿里云STS接入地址。
于是接下来我需要自己来拼接访问STS的访问地址,官方也给了使用签名的文档,但是感觉不是很好。所以把自己的摸索历程记下来。
实现代码
在阿里云官方的签名机制文档中,给出了详细的获取签名的步骤,以下代码中有详细的说明。
private static String DEMO_ARN = "acs:ram::1234567890123:role/firstrole";
private static String DEMO_SESSION_NAME = "client";
private static String DEMO_ACCESS_KEY_ID = "testid";
private static String DEMO_ACCESS_KEY_SECRET = "testsecret";
private static String DEMO_UUID = "571f8fb8-506e-11e5-8e12-b8e8563dc8d2";
private static String DEMO_ISO8601TIME = "2015-09-01T05:57:34Z";
public static void getSecurityToken() throws NoSuchAlgorithmException, IOException, InvalidKeyException {
System.out.println("步骤一、1.使用请求参数构造规范化的请求字符串(Canonicalized Query String)。");
//阿里云提供的获取ISO8601格式时间的方法
String iso8601Time = ParameterHelper.getISO8601Time(new Date());
String uuid = getUUID();
//按照文档的说明,要先将参数按照字典顺序排序,然后对参数进行看起来很复杂的编码
//但实际上已经提供了现成的方法进行编码:percentEncode
//keyList是我自己手动进行排序的参数key值的集合,下面的valueList按顺序对应他们的取值
List<String> keyList = new ArrayList<>();
keyList.add("AccessKeyId");
keyList.add("Action");
keyList.add("Format");
keyList.add("RoleArn");
keyList.add("RoleSessionName");
keyList.add("SignatureMethod");
keyList.add("SignatureNonce");
keyList.add("SignatureVersion");
keyList.add("Timestamp");
keyList.add("Version");
List<String> valueList = new ArrayList<>();
valueList.add(DEMO_ACCESS_KEY_ID);
valueList.add("AssumeRole");
valueList.add("JSON");
valueList.add(DEMO_ARN);
valueList.add(DEMO_SESSION_NAME);
valueList.add("HMAC-SHA1");
valueList.add(DEMO_UUID);
valueList.add("1.0");
valueList.add(DEMO_ISO8601TIME);
valueList.add("2015-04-01");
StringBuilder url = new StringBuilder();
//这里对参数的key和value进行编码,使用的方法是官方提供的percentEncode方法
for(int i = 0; i < keyList.size(); i ++){
String key = keyList.get(i);
String value = valueList.get(i);
if(i > 0){
url.append("&");
}
url.append(percentEncode(key)).append("=").append(percentEncode(value));
}
//url这个变量存储了获取签名必须的内容。
System.out.println("CQS=" + url.toString());
System.out.println("");
System.out.println("2.将构造的规范化字符串按照下面的规则构造成待签名的字符串。");
//要注意的是,官方文档中给出的示例URL包含https://sts.aliyuncs.com/?
//但实际上要进行stringToSign的只是?之后的内容。
String stringToSign = "GET&" + percentEncode("/") + "&" + percentEncode(url.toString());
System.out.println("待签名的字符串=" + stringToSign);
System.out.println("");
System.out.println("步骤二、1.按照RFC2104的定义,计算待签名字符串(StringToSign)的HMAC值。");
System.out.println("期望值:gNI7b0AyKZHxDgjBGPDgJ1Ce3L4=");
//这里调用官方提供的方法获取签名值
//我的日志里可以看出这里并没有官方文档中步骤二里的第二个小步骤
//因为其实这里算出来的HMAC值,就已经是Signature的值了
//我之前就是又对HMAC值进行了Base64编码,所以生成的地址一直提示签名不匹配
//原因是因为官方的signString方法中已经进行了Base64编码
HmacSHA1Signer sha1Signer = new HmacSHA1Signer();
String ShaHmac2Res = sha1Signer.signString(stringToSign, DEMO_ACCESS_KEY_SECRET + "&");
System.out.println("HMAC值2=" + ShaHmac2Res);
System.out.println("");
System.out.println("3.将得到的签名值作为Signature参数添加到请求参数中。");
StringBuilder urlOfSign = new StringBuilder();
urlOfSign.append("https://sts.aliyuncs.com/").append("?").append(url).append("&Signature=").append(percentEncode(ShaHmac2Res));
System.out.println("HMAC值2的URL=" + urlOfSign.toString());
//上面的urlOfSign就是最终有了签名的访问地址。
System.out.println("over");
}