3

阿里云短信服务 HTTP 协议签名 golang 版

 2 years ago
source link: https://blog.wolfogre.com/posts/send-sms-by-aliyun-in-golang/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

阿里云短信服务 HTTP 协议签名 golang 版

2017/11/20.

Golang , Aliyun 0.6k+ 0

最近写一个小服务,需要用到发短信的功能。于是打开阿里云的短信服务界面,发现它又双叒叕升级了,印象里好像是第三次还是第四次升级了。升级后接口有些变化,官方为新接口提供了常用语言的 SDK,然而并没有 golang 版的,好在官方文档里提供了 java 版的 HTTP 签名 demo

参照该 demo 我写了一个 golang 版,亲测有效。然而因为懒我并不打算封装成 golang 版的 SDK(除非阿里云给我点买服务器的优惠券……),所以这里仅贴出代码供参考。

首先官方的 java 源码如下:

public class SignDemo {
    public static void main(String[] args) throws Exception {
        String accessKeyId = "testId";
        String accessSecret = "testSecret";
        java.text.SimpleDateFormat df = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        df.setTimeZone(new java.util.SimpleTimeZone(0, "GMT"));// 这里一定要设置GMT时区
        java.util.Map<String, String> paras = new java.util.HashMap<String, String>();
        // 1. 系统参数
        paras.put("SignatureMethod", "HMAC-SHA1");
        paras.put("SignatureNonce", java.util.UUID.randomUUID().toString());
        paras.put("AccessKeyId", accessKeyId);
        paras.put("SignatureVersion", "1.0");
        paras.put("Timestamp", df.format(new java.util.Date()));
        paras.put("Format", "XML");
        // 2. 业务API参数
        paras.put("Action", "SendSms");
        paras.put("Version", "2017-05-25");
        paras.put("RegionId", "cn-hangzhou");
        paras.put("PhoneNumbers", "15300000001");
        paras.put("SignName", "阿里云短信测试专用");
        paras.put("TemplateParam", "{\"customer\":\"test\"}");
        paras.put("TemplateCode", "SMS_71390007");
        paras.put("OutId", "123");
        // 3. 去除签名关键字Key
        if (paras.containsKey("Signature"))
            paras.remove("Signature");
        // 4. 参数KEY排序
        java.util.TreeMap<String, String> sortParas = new java.util.TreeMap<String, String>();
        sortParas.putAll(paras);
        // 5. 构造待签名的字符串
        java.util.Iterator<String> it = sortParas.keySet().iterator();
        StringBuilder sortQueryStringTmp = new StringBuilder();
        while (it.hasNext()) {
            String key = it.next();
            sortQueryStringTmp.append("&").append(specialUrlEncode(key)).append("=").append(specialUrlEncode(paras.get(key)));
        }
        String sortedQueryString = sortQueryStringTmp.substring(1);// 去除第一个多余的&符号
        StringBuilder stringToSign = new StringBuilder();
        stringToSign.append("GET").append("&");
        stringToSign.append(specialUrlEncode("/")).append("&");
        stringToSign.append(specialUrlEncode(sortedQueryString));
        String sign = sign(accessSecret + "&", stringToSign.toString());
        // 6. 签名最后也要做特殊URL编码
        String signature = specialUrlEncode(sign);
        System.out.println(paras.get("SignatureNonce"));
        System.out.println("\r\n=========\r\n");
        System.out.println(paras.get("Timestamp"));
        System.out.println("\r\n=========\r\n");
        System.out.println(sortedQueryString);
        System.out.println("\r\n=========\r\n");
        System.out.println(stringToSign.toString());
        System.out.println("\r\n=========\r\n");
        System.out.println(sign);
        System.out.println("\r\n=========\r\n");
        System.out.println(signature);
        System.out.println("\r\n=========\r\n");
        // 最终打印出合法GET请求的URL
        System.out.println("http://dysmsapi.aliyuncs.com/?Signature=" + signature + sortQueryStringTmp);
    }
    public static String specialUrlEncode(String value) throws Exception {
        return java.net.URLEncoder.encode(value, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
    }
    public static String sign(String accessSecret, String stringToSign) throws Exception {
        javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA1");
        mac.init(new javax.crypto.spec.SecretKeySpec(accessSecret.getBytes("UTF-8"), "HmacSHA1"));
        byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"));
        return new sun.misc.BASE64Encoder().encode(signData);
    }
}

参考该代码写的 golang 版的:

package main

import (
	"sort"
	"time"
	"fmt"
	"crypto/hmac"
	"crypto/sha1"
	"math/rand"
	"net/url"
	"strings"
	"encoding/base64"
)

func main() {
	accessKeyId := "testId"
	accessSecret := "testSecret"

	timezone, err := time.LoadLocation("GMT0") // 这里一定要设置GMT时区
	if err != nil {
		panic(err)
	}

	paras := make(map[string]string)

	// 1. 系统参数
	paras["SignatureMethod"] = "HMAC-SHA1"
	paras["SignatureNonce"] = fmt.Sprintf("%v", randomString(16)) // 原例子中是使用 UUID,但 golang 原生包里并没有支持,故用随机字符串代替
	paras["AccessKeyId"] = accessKeyId
	paras["SignatureVersion"] = "1.0"
	paras["Timestamp"] = time.Now().In(timezone).Format("2006-01-02T15:04:05Z")
	paras["Format"] = "XML"

	// 2. 业务参数
	paras["Action"] = "SendSms"
	paras["Version"] = "2017-05-25"
	paras["RegionId"] = "cn-hangzhou"
	paras["PhoneNumbers"] = "15300000001"
	paras["SignName"] = "阿里云短信测试专用"
	paras["TemplateCode"] = `{"customer":"test"}`
	paras["TemplateParam"] = "SMS_71390007"
	paras["OutId"] = "123"

	// 3. 去除签名关键字Key
	delete(paras, "Signature")

	// 4. 参数KEY排序
	parasIndex := make([]string, 0)
	for k := range paras {
		parasIndex = append(parasIndex, k)
	}
	sort.Strings(parasIndex)

	// 5. 构造待签名的字符串
	sortedQueryString := ""
	for _, v := range parasIndex {
		sortedQueryString = sortedQueryString + "&" + specialUrlEncode(v) + "=" + specialUrlEncode(paras[v])
	}

	// 去除第一个多余的&符号
	sortedQueryString = sortedQueryString[1:]

	stringToSign := "GET" + "&" + specialUrlEncode("/") + "&" + specialUrlEncode(sortedQueryString)

	signStr := sign(accessSecret  + "&", stringToSign)

	// 6. 签名最后也要做特殊URL编码
	signature := specialUrlEncode(signStr)

	// 最终打印出合法GET请求的URL
	urlStr := "http://dysmsapi.aliyuncs.com/?Signature=" + signature + "&" + sortedQueryString
	fmt.Println(urlStr)
}

func randomString(length int) string {
	base := "abcdefghijklmnopqrstuvwxyz1234567890"
	result := ""
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	for len(result) < length {
		index := r.Intn(len(base))
		result = result + base[index : index + 1]
	}
	return result
}

func specialUrlEncode(value string) string {
	result := url.QueryEscape(value)
	result = strings.Replace(result, "+", "%20", -1)
	result = strings.Replace(result, "*", "%2A", -1)
	result = strings.Replace(result, "%7E", "~", -1)
	return result
}

func sign(accessSecret, strToSign string) string {
	mac := hmac.New(sha1.New, []byte(accessSecret))
	mac.Write([]byte(strToSign))
	signData := mac.Sum(nil)
	return base64.StdEncoding.EncodeToString(signData)
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK