签名机制
ROA风格API签名包含两部分:公共请求头(HTTP 协议 header 参数和阿里云协议 Header 参数)和 CanonicalizedResource(规范资源)。推荐尽量使用SDK接入,用户可以不关注签名细节。
名称 | 描述 |
Authorization | 用于验证请求合法性的认证信息,采用 acs AccessKeyId:signature 格式。 |
Content-Length | RFC 2616 中定义的 HTTP 请求内容长度。 |
Content-Type | RFC 2616 中定义的 HTTP 请求内容类型。 |
Content-MD5 | HTTP 协议消息体的 128-bit MD5 散列值转换成 BASE64 编码的结果。为了防止所有请求被篡改,建议所有请求都附加该信息。 |
Date | 描述请求时间,GMT 格式,如:Wed, 26 Aug. 2015 17:01:00 GMT。 |
Accept | 客户端需要的返回值类型,只支持 application/json |
Host | 访问 Host 值,例如:mt.cn-hangzhou.aliyuncs.com。 |
x-acs-signature-nonce | 唯一随机数,用于防止网络重放攻击。用户在不同请求中要使用不同的随机数值。 |
x-acs-signature-method | 签名方法,目前只支持 HMAC-SHA1。 |
x-acs-version | API版本, 如果不填写,服务端默认取最高版本 |
签名计算方法
ROA风格的API请求使用标准的Authorization头来签名自己的请求,请求格式如下:
Authorization: acs AccessKeyId:Signature
计算body的MD5值,然后再对其进行base64编码,编码后的值设置到 Header中。
使用请求中的Header参数构造规范化的Header字符串
headerStringToSign = HTTP-Verb + "\n" + //HTTP_Verb只支持POST Accept + "\n" + //Accept为application/json Content-MD5 + "\n" + //第1步中计算出来的MD5值 Content-Type + "\n" + //Content-Type值为application/json;chrset=utf-8 Date + "\n" + //Date值为GMT时间 “x-acs-signature-method:HMAC-SHA1\n” + “x-acs-signature-nonce:” + ${x-acs-signature-nonce} + "\n" + “x-acs-version:2019-01-02" + "\n";
CanonicalizedResource 表示客户想要访问资源的规范描述,需要将子资源和qurey一同按照字典序,从小到大排列并以 & 为分隔符生成子资源字符串(?后的所有参数),示例如下(alimt所有请求都不带参数)。
resourceStringToSign = URI;
将上两步构造的规范化字符串按照下面的规则构造成待签名的字符串。
stringToSign = headerStringToSign + resourceStringToSign;
按照 RFC2104的定义,计算待签名字符串StringToSign的 HMAC 值,按照 Base64 编码规则把上面的 HMAC 值编码成字符串,并在前面加上AccessKeyId,即得到签名值(Authorization),示例如下:
Signature = Base64( HMAC-SHA1( AccessSecret, UTF-8-Encoding-Of(StringToSign) ) ) Authorization = "acs " + AccessKeyId + ":" + Signature
代码示例(java)
import sun.misc.BASE64Encoder;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.UUID;
public class Sender {
/*
* 计算MD5+BASE64
*/
public static String MD5Base64(String s) {
if (s == null)
return null;
String encodeStr = "";
byte[] utfBytes = s.getBytes();
MessageDigest mdTemp;
try {
mdTemp = MessageDigest.getInstance("MD5");
mdTemp.update(utfBytes);
byte[] md5Bytes = mdTemp.digest();
BASE64Encoder b64Encoder = new BASE64Encoder();
encodeStr = b64Encoder.encode(md5Bytes);
} catch (Exception e) {
throw new Error("Failed to generate MD5 : " + e.getMessage());
}
return encodeStr;
}
/*
* 计算 HMAC-SHA1
*/
public static String HMACSha1(String data, String key) {
String result;
try {
SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(data.getBytes());
result = (new BASE64Encoder()).encode(rawHmac);
} catch (Exception e) {
throw new Error("Failed to generate HMAC : " + e.getMessage());
}
return result;
}
/*
* 获取时间
*/
public static String toGMTString(Date date) {
SimpleDateFormat df = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z", Locale.UK);
df.setTimeZone(new java.util.SimpleTimeZone(0, "GMT"));
return df.format(date);
}
/*
* 发送POST请求
*/
public static String sendPost(String url, String body, String ak_id, String ak_secret) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
/*
* http header 参数
*/
String method = "POST";
String accept = "application/json";
String content_type = "application/json;chrset=utf-8";
String path = realUrl.getFile();
String date = toGMTString(new Date());
String host = realUrl.getHost();
// 1.对body做MD5+BASE64加密
String bodyMd5 = MD5Base64(body);
String uuid = UUID.randomUUID().toString();
String stringToSign = method + "\n" + accept + "\n" + bodyMd5 + "\n" + content_type + "\n" + date + "\n"
+ "x-acs-signature-method:HMAC-SHA1\n"
+ "x-acs-signature-nonce:" + uuid + "\n"
+ "x-acs-version:2019-01-02\n"
+ path;
// 2.计算 HMAC-SHA1
String signature = HMACSha1(stringToSign, ak_secret);
// 3.得到 authorization header
String authHeader = "acs " + ak_id + ":" + signature;
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("Accept", accept);
conn.setRequestProperty("Content-Type", content_type);
conn.setRequestProperty("Content-MD5", bodyMd5);
conn.setRequestProperty("Date", date);
conn.setRequestProperty("Host", host);
conn.setRequestProperty("Authorization", authHeader);
conn.setRequestProperty("x-acs-signature-nonce", uuid);
conn.setRequestProperty("x-acs-signature-method", "HMAC-SHA1");
conn.setRequestProperty("x-acs-version", "2019-01-02"); // 版本可选
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// 发送请求参数
out.print(body);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
InputStream is;
HttpURLConnection httpconn = (HttpURLConnection) conn;
if (httpconn.getResponseCode() == 200) {
is = httpconn.getInputStream();
} else {
is = httpconn.getErrorStream();
}
in = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输出流、输入流
finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return result;
}
}