签名机制

  1. 使用请求参数构造规范化的请求字符串(Canonicalized Query String)

    1. 按照参数名称的字典顺序,对请求中所有的请求参数(不能包括F_sign参数)进行排序。
    2. 对每个请求参数的名称和值进行编码。名称和值要使用 UTF-8 字符集进行 URL 编码,URL 编码的编码规则是:
      1. 对于字符 A-Z、a-z、0-9 以及字符 “-”、“_”、“.”、“~” 不编码
      2. 注意:一般支持 URL 编码的库都是按照 application/x-www-form-urlencoded 的 MIME 类型的规则进行编码。可以直接使用这类方式进行编码,把编码后的字符串中加号(+)替换成 %20、星号(*)替换成 %2A%7E 替换回波浪号(~),即可得到上述规则描述的编码字符串
    3. 对编码后的参数名称和值使用半角的等号(=)进行连接。
    4. 再把半角的等号连接得到的字符串按参数名称的字典顺序,依次使用 “&” 符号连接,即得到规范化请求字符串。
  2. 按照 RFC2104 的定义,使用上面的构造的请求字符串计算签名 HMAC 值(使用的哈希算法是sha1算法)。

    1. 注意:计算签名的signKey的计算是区分版本的
      1. "01"版本 signKey = F_accesstoken
      2. "02"版本 signKey = REQUEST_METHOD+"&"+URLENCODE("/")+"&"+F_accesstoken , 注意REQUEST_METHOD是大写得请求,如: GET,POST
  3. 上面计算得到的HAMC值,经过base64加密后,在字符串前加上版本号

    1. "01"+base64(hmac)
    2. "02"+base64(hmac)
  4. 经过第3步后得到得就是签名,赋值给 F_sign参数

  5. 字符 - URL编码值

  6. 生成代码示例

func reqSign() string {

    accessToken := "someToken"

    // 请求参数
    p := url.Values{}
    p.Set("F_param_a", "value_a")
    p.Set("F_param_b", "value_b")
    p.Set("F_accesstoken", accessToken)

    // 将参数按字母表顺序排列,并 URL encode 参数值
    // 注:鉴于不会有人使用会被编码的字符命名参数(如果有,吊起来打到没有),此处省略对参数名的 URL encode 操作
    e := p.Encode()

    e = strings.Replace(e, "+", "%20", -1) // "+" -> "%20"
    e = strings.Replace(e, "*", "%2A", -1) // "*" -> "%2A"
    e = strings.Replace(e, "%7E", "~", -1) // "%7E" -> "~"

    // signKey ver 1
    signKey := accessToken
    // signKey ver 2
    // method := http.MethodGet
    // signKey := method + "&" + url.QueryEscape("/") + "&" + accessToken

    // 计算hmac
    mac := hmac.New(sha1.New, []byte(signKey))
    mac.Write([]byte(e))
    m := mac.Sum(nil)

    // base64url encode
    b64Hmac := base64.URLEncoding.EncodeToString(m)
    // 签名版本: "01" or "02"
    signVer := "01"

    return signVer + b64Hmac
}
// base64 编码转为 base64url 编码
// @param base64Str string
// @return string
let encode64To64URL = (base64Str) => {
    return base64Str.replace(/\+/g, '-').replace(/\//g, '_');
}

// URL encode 参数名/参数值
// "+" -> "%20"
// "*" -> "%2A"
// "%7E" -> "~"
// @param raw string
// @return string
let encode = (raw) => {
    return encodeURIComponent(raw).
    replace(/\+/g, "%20").
    replace(/\*/g, "%2A").
    replace(/%7E/g, "~");
}

// 构造请求签名
// @return string
let reqSign = () => {

    let accesstoken = "someToken";

    // 参数
    let p = new URLSearchParams()
    p.append(encode("F_param_a"), encode("val_a"));
    p.append(encode("F_param_b"), encode("val_b"));
    p.append(encode("F_accesstoken"), encode(accesstoken));
    p.sort();

    paramsArray = [];
    for (let kv of p.entries()){
        paramsArray.push(kv[0]+"="+kv[1]);
    }
    let msg = paramsArray.join("&");

    // 此处使用 https://github.com/digitalbazaar/forge 的 hmac 实现
    let signKey = accesstoken;
    let hmac = forge.hmac.create();
    hmac.start('sha1', signKey);
    hmac.update(msg);

    let signVer = "01";

    return signVer + encode64To64URL(btoa(hmac.digest().data));
}