物联网络管理平台会对每个接口访问请求的发送者进行身份验证,所以无论使用 HTTP 还是 HTTPS 协议提交请求,都需要在请求中包含签名(Signature)信息。

签名时,您需在控制台 AccessKey 管理 页面查看您的阿里云账号的 AccessKeyId 和 AccessKeySecret,然后进行对称加密。其中,AccessKeyId 用于标识访问者身份;AccessKeySecret 是用于加密签名字符串和服务器端验证签名字符串的密钥,必须严格保密。

  • 字符大写字母(A~Z)、小写字母(a~z)、数字(0~9)以及半字线(-)、下划线(_)、点号(.)、波浪线(~)不编码。
  • 其它字符编码成 %XY 的格式,其中 XY 是字符对应ASCII码的16进制表示。例如英文的双引号 " 对应的编码为 %22
  • 扩展的 UTF-8 字符,编码成 %XY%ZA... 的格式。
  • 英文空格要编码成 %20 ,而不是加号 +

    如果您使用的是 Java 标准库中的 java.net.URLEncoder ,可以先用 encode 编码,随后将编码后的字符中加号 + 替换为 %20 、星号 * 替换为 %2A %7E 替换回波浪号 ~ ,即可得到上述规则描述的编码字符串。

    private static final String ENCODING = "UTF-8";
    private static String percentEncode(String value) throws UnsupportedEncodingException {
        return value != null ? URLEncoder.encode(value, ENCODING).replace("+", "%20").replace("*", "%2A").replace("%7E", "~") : null;
                            
  • 使用等号=连接编码后的请求参数名和参数值。
  • 使用与号&连接编码后的请求参数。参数排序与步骤“排序参数”的描述一致。

    可以使用percentEncode处理步骤 1 得到的规范化字符串,构造签名字符串。可参考如下规则。

    String stringToSign = httpMethod + "&" + // httpMethod: 发送请求所采用的 HTTP 方法,例如 GET。
                          percentEncode("/") + "&" + // percentEncode("/"): 字符'/'UTF-8 编码得到的值,即 %2F。
                          percentEncode(canonicalizedQueryString) // 您的规范化请求字符串。                
  • 计算 HMAC 值。

    按照RFC2104的定义,使用步骤 2 得到的字符串stringToSign计算签名 HMAC 值。下列伪代码演示了获得 HMAC 值的方法。

    HMAC-Value = HMAC-SHA1 ( AccessSecret, UTF-8-Encoding-Of ( StringToSign ) )                
    说明 计算HMAC 值时,使用的 Key 就是您的AccessKeySecret并加上一个与号&字符(ASCII:38)。使用的哈希算法是SHA1。
  • 计算得到的待签名字符串StringToSign
    GET&%2F&AccessKeyId%3Dtestid&Action%3DGetGateway&Format%3DJSON&GwEui%3D0000000000000000&RegionId%3Dcn-shanghai&SignatureMethod%3DHMAC-SHA1&SignatureNonce%3D15215528852396&SignatureVersion%3D1.0&Timestamp%3D2019-01-20T12%253A00%253A00Z&Version%3D2019-01-20                    
  • 计算签名值。

    因为AccessKeySecret = testsecret,用于计算的 Key 为testsecret&,计算得到的签名值为:

    yqWsF0aPGrECmuwTfALUIl0JM9M%3D                 
  • 将签名作为 Signature 参数加入到 URL 请求中,最后得到的 URL 为:
    https://linkwan.cn-shanghai.aliyuncs.com/
    ?Format=JSON
    &Version=2019-01-20
    &Signature=yqWsF0aPGrECmuwTfALUIl0JM9M%3D
    &SignatureMethod=HMAC-SHA1
    &SignatureNonce=15215528852396
    &SignatureVersion=1.0
    &AccessKeyId=testid
    &Timestamp=2019-01-20T12:00:00Z
    &RegionId=cn-shanghai
    &Action=GetGateway
    &GwEui=0000000000000000                    
    * 阿里云账号的 Access Key Secret。 public static final String ACCESS_KEY_SECRET = "testsecret"; * UTF-8 字符集。 public static final String CHARSET_UTF8 = "utf8";
  • UrlUtil.java
    package aliyun.signature;
    import java.net.URLEncoder;
    import java.util.Map;
     * URL 处理工具。
     * @author Alibaba Cloud
     * @date 2019/01/20
    public class UrlUtil {
         * UTF-8 编码。
        private final static String CHARSET_UTF8 = "utf8";
         * 编码 URL。
         * @param url 要编码的 URL。
         * @return 编码后的 URL。
        public static String urlEncode(String url) {
            if (url != null && !url.isEmpty()) {
                try {
                    url = URLEncoder.encode(url, "UTF-8");
                } catch (Exception e) {
                    System.out.println("Url encoding error:" + e.getMessage());
            return url;
         * 规范化请求串。
         * @param params 请求中所有参数的 KV 对。
         * @param shouldEncodeKv 是否需要编码 KV 对中的文本。
         * @return 规范化的请求串。
        public static String canonicalizeQueryString(Map<String, String> params, boolean shouldEncodeKv) {
            StringBuilder canonicalizedQueryString = new StringBuilder();
            for (Map.Entry<String, String> entry : params.entrySet()) {
                if (shouldEncodeKv) {
                    canonicalizedQueryString.append(percentEncode(entry.getKey()))
                                            .append("=")
                                            .append(percentEncode(entry.getValue()))
                                            .append("&");
                } else {
                    canonicalizedQueryString.append(entry.getKey())
                                            .append("=")
                                            .append(entry.getValue())
                                            .append("&");
            if (canonicalizedQueryString.length() > 1) {
                canonicalizedQueryString.setLength(canonicalizedQueryString.length() - 1);
            return canonicalizedQueryString.toString();
         * 对原文进行百分号编码。
         * @param text 原文。
         * @return 编码结果。
        public static String percentEncode(String text) {
            try {
                return text == null
                       ? null
                       : URLEncoder.encode(text, CHARSET_UTF8)
                                   .replace("+", "%20")
                                   .replace("*", "%2A")
                                   .replace("%7E", "~");
            } catch (Exception e) {
                System.out.println("Percentage encoding error:" + e.getMessage());
            return "";
                         
  • SignatureUtils.java
    package aliyun.signature;
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    import java.net.URI;
    import java.net.URISyntaxException;
    import java.net.URLDecoder;
    import java.net.URLEncoder;
    import java.util.Map;
    import java.util.TreeMap;
    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    import org.apache.commons.codec.binary.Base64;
     * API 签名工具。
     * @author Alibaba Cloud
     * @date 2019/01/20
    public class SignatureUtils {
        private final static String CHARSET_UTF8 = "utf8";
        private final static String ALGORITHM = "UTF-8";
        private final static String SEPARATOR = "&";
        private final static String METHOD_NAME_POST = "POST";
         * 分解请求串中的参数。
         * @param url 原始 URL。
         * @return 分解后的参数名、参数值对的映射
        public static Map<String, String> splitQueryString(String url)
                throws URISyntaxException,
                       UnsupportedEncodingException {
            URI uri = new URI(url);
            String query = uri.getQuery();
            final String[] pairs = query.split("&");
            TreeMap<String, String> queryMap = new TreeMap<String, String>();
            for (String pair : pairs) {
                final int idx = pair.indexOf("=");
                final String key = idx > 0 ? pair.substring(0, idx) : pair;
                if (!queryMap.containsKey(key)) {
                    queryMap.put(key, URLDecoder.decode(pair.substring(idx + 1), CHARSET_UTF8));
            return queryMap;
         * 计算签名名转译成合适的编码。
         * @param httpMethod HTTP 请求的 Method。
         * @param parameter 原始请求串中参数名、参数值对的映射。
         * @param accessKeySecret 阿里云账号的 Access Key Secret。
         * @return 转译成合适编码的签名。
        public static String generate(String httpMethod, Map<String, String> parameter, String accessKeySecret)
                throws Exception {
            String stringToSign = generateStringToSign(httpMethod, parameter);
            System.out.println("signString---" + stringToSign);
            byte[] signBytes = hmacSHA1Signature(accessKeySecret + "&", stringToSign);
            String signature = newStringByBase64(signBytes);
            if (signature == null) {
                return "";
            System.out.println("signature----" + signature);
            if (METHOD_NAME_POST.equals(httpMethod)) {
                return signature;
            return URLEncoder.encode(signature, ALGORITHM);
         * 计算签名中间产物 StringToSign。
         * @param httpMethod HTTP 请求的 Method。
         * @param parameter 原始请求串中参数名、参数值对的映射。
         * @return 签名中间产物 StringToSign。
        public static String generateStringToSign(String httpMethod, Map<String, String> parameter)
                throws IOException {
            TreeMap<String, String> sortParameter = new TreeMap<String, String>(parameter);
            String canonicalizedQueryString = UrlUtil.canonicalizeQueryString(sortParameter, true);
            if (httpMethod == null || httpMethod.isEmpty()) {
                throw new RuntimeException("httpMethod can not be empty");
            StringBuilder stringToSign = new StringBuilder();
            stringToSign.append(httpMethod).append(SEPARATOR);
            stringToSign.append(percentEncode("/")).append(SEPARATOR);
            stringToSign.append(percentEncode(canonicalizedQueryString));
            return stringToSign.toString();
         * 对原文进行百分号编码处理。
         * @param text 要处理的原文。
         * @return 处理后的百分号编码。
        public static String percentEncode(String text) {
            try {
                return text == null ? null
                        : URLEncoder.encode(text, CHARSET_UTF8)
                                    .replace("+", "%20")
                                    .replace("*", "%2A")
                                    .replace("%7E", "~");
            } catch (Exception e) {
                System.out.println("Percentage encoding error:" + e.getMessage());
            return "";
         * HMAC-SHA1 键控散列。
         * @param secret HMAC-SHA1 使用的 Secret。
         * @param baseString 原文。
         * @return 散列值。
        public static byte[] hmacSHA1Signature(String secret, String baseString)
                throws Exception {
            if (secret == null || secret.isEmpty()) {
                throw new IOException("secret can not be empty");
            if (baseString == null || baseString.isEmpty()) {
                return null;
            Mac mac = Mac.getInstance("HmacSHA1");
            SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(CHARSET_UTF8), ALGORITHM);
            mac.init(keySpec);
            return mac.doFinal(baseString.getBytes(CHARSET_UTF8));
         * Base 64 编码。
         * @param bytes 原文。
         * @return Base 64 编码。
        public static String newStringByBase64(byte[] bytes)
                throws UnsupportedEncodingException {
            if (bytes == null || bytes.length == 0) {
                return null;
            return new String(Base64.encodeBase64(bytes, false), CHARSET_UTF8);
                         
  • DemoApplication.java
    package aliyun.signature;
    import java.io.UnsupportedEncodingException;
    import java.net.URLEncoder;
    import java.util.HashMap;
    import java.util.Map;
     * API 签名 Demo 主程序。
     * @author Alibaba Cloud
     * @date 2019/01/20
    public class DemoApplication {
         * 1. 需求修改 Config.java 中的 Access Key 信息。
         * 2. 建议使用方法二,所有参数都需要一一填写。
         * 3. "最终 Signature"才是你需要的签名最终结果。
         * @param args ...
        public static void main(String[] args) throws UnsupportedEncodingException {
            // 方法一。
            System.out.println("方法一:");
            String str = "GET&%2F&AccessKeyId%3D" + Config.ACCESS_KEY_ID
                    + "&Action%3DGetGateway&Format%3DJSON&GwEui%3D"
                    + "0000000000000000&RegionId%3Dcn-shanghai&Signa"
                    + "tureMethod%3DHMAC-SHA1&SignatureNonce%3D1521552"
                    + "8852396&SignatureVersion%3D1.0&Timestamp%3D20"
                    + "19-01-20T12%253A00%253A00Z&Version%3D2019-01-20";
            byte[] signBytes;
            try {
                signBytes = SignatureUtils.hmacSHA1Signature(Config.ACCESS_KEY_SECRET + "&", str.toString());
                String signature = SignatureUtils.newStringByBase64(signBytes);
                System.out.println("signString---" + str);
                System.out.println("signature----" + signature);
                System.out.println("最终signature:" + URLEncoder.encode(signature, Config.CHARSET_UTF8));
            } catch (Exception e) {
                e.printStackTrace();
            System.out.println();
            // 方法二。
            System.out.println("方法二:");
            Map<String, String> map = new HashMap<String, String>();
            // 公共参数。
            map.put("Format", "JSON");
            map.put("Version", "2019-01-20");
            map.put("AccessKeyId", Config.ACCESS_KEY_ID);
            map.put("SignatureMethod", "HMAC-SHA1");
            map.put("Timestamp", "2019-01-20T12:00:00Z");
            map.put("SignatureVersion", "1.0");
            map.put("SignatureNonce", "15215528852396");
            map.put("RegionId", "cn-shanghai");
            map.put("Action", "GetGateway");
            // 请求参数。
            map.put("GwEui", "0000000000000000");
            try {
                String signature = SignatureUtils.generate("GET", map, Config.ACCESS_KEY_SECRET);
                System.out.println("最终signature:" + signature);
            } catch (Exception e) {
                e.printStackTrace();
            System.out.println();
    
  •