文章目录
背景
当前平台服务端接入的鉴权方式是【鉴权指南 md5 - v1.0】,由于该签名方式并未使用通用的方式来处理一些复杂的参数类型(如:list、object 等),而是依赖了语言库的一些特性来实现,也就是说,不同语言库的实现方式可能存在差异,导致在复杂参数类型上并不适用。为了解决这个问题,平台对此做出了优化,引入了【鉴权指南 md5 - v1.1】。
与v1.0版本的签名方式相比,有什么不同
对于参数的 value 值转换做了如下定义:
类型 | v1.0定义 | v1.1定义 |
---|---|---|
string | 直接拼接字符串内容即可,没有引号,如:abc |
与v1.0相同 |
number | 需要转成字符串后拼接,如:123 、1.03 |
与v1.0相同 |
bool | 需要转成 true /false 后拼接 |
与v1.0相同 |
空(null) | 需要转成空字符串("" )后拼接 |
与v1.0相同 |
list | 一般会转为 string 的方式来使用,不会直接使用 list 结构本身 | 与v1.0不同,需要转成 json 类型的 array 后拼接,如: [1, 2, 3] |
object | 一般会转为 string 的方式来使用,不会直接使用 object 结构本身 | 与v1.0不同,需要转成 json 类型的 object 后拼接,如:{"age":20,"height":170.3,"name":"张三"} |
对于复杂结构(list、object),内嵌的各种类型都以 json 为标准,针对v1.1的list
的类型举例:
类型 | 例子 |
---|---|
number list | [1,2,3] |
string list | ["a","c"] |
object list | [{"age":20,"name":"张三"},{"age":28,"name":"李四"}] |
注意:复杂结构的 JSON 串不需要对内部字段进行拆分和排序,但要保证签名时对复杂结构的 JSON 序列化方式要和 HTTP 发起 JSON 请求的序列化方式一致。
使用方式
签名算法
MD5
待签名参数
获取所有请求参数(防止新增参数导致签名失败),除去 sign
,其他请求参数(包含空值的参数,即 key=""
)都要参加验签。
详细步骤
STEP1:获取签名密钥
STEP2:参数拼接,获取 $source
字符串
- 对所有待签名参数按照字段名的ASCII码从小到大排序;
- 使用键值对的格式(
key1=value1&key2=value2
)连接参数,拼接成字符串; - 对拼接完参数的字符串进行
urlencode
编码,获得$source
字符串;
STEP3:利用密钥生成请求参数的签名摘要
- 将上述
$source
字符串与秘钥AppSecret(签名密钥)
通过&
符号进行拼接,即:$source
&AppSecret(签名密钥)
; - 将拼接后的字符串进行MD5哈希签名得到最终
sign
值(长度为32); - 将
sign
值作为请求参数的一部分,发送到服务端进行鉴权。
使用举例
假设 AppSecret(签名密钥)
为 38f9c7af24ff11edb92900163e30ef81
,请求参数为:
- b = 1
- a = "飞鱼"
- d = 0.1
- c =
null
- e = [1,2,3]
- f = {"g":"h","i":1}
- x =
true
- y =
false
实际计算步骤如下:
- 通过步骤2的排序和连接,可以得到:
a=飞鱼&b=1&c=&d=0.1&e=[1,2,3]&f={"g":"h","i":1}&x=true&y=false
- 通过步骤2的
urlencode
编码,可以得到a%3D%E9%A3%9E%E9%B1%BC%26b%3D1%26c%3D%26d%3D0.1%26e%3D%5B1%2C2%2C3%5D%26f%3D%7B%22g%22%3A%22h%22%2C%22i%22%3A1%7D%26x%3Dtrue%26y%3Dfalse
- 通过步骤3的尾部追加
App Secret(签名密钥)
,可以得到:a%3D%E9%A3%9E%E9%B1%BC%26b%3D1%26c%3D%26d%3D0.1%26e%3D%5B1%2C2%2C3%5D%26f%3D%7B%22g%22%3A%22h%22%2C%22i%22%3A1%7D%26x%3Dtrue%26y%3Dfalse&38f9c7af24ff11edb92900163e30ef81
- 计算 MD5 哈希值,得到
sign
值:c30223cb4b65b611300ffc15c8d7babb
签名样例
Golang
package main
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"net/url"
"sort"
"strings"
)
func Sign(params map[string]interface{}, appSecret string) string {
keys := make([]string, 0, len(params))
for k := range params {
keys = append(keys, k)
}
// 对key进行排序
sort.Strings(keys)
var kvPairs []string
for _, k := range keys {
if k == "sign" {
continue
}
v := params[k]
strValue := ""
if v != nil {
switch v := v.(type) {
case string: // 单独处理字符串类型,防止被双引号包裹
strValue = v
default: // 通过 json 序列化处理其他的类型值
jsonBytes, _ := json.Marshal(v)
strValue = string(jsonBytes)
}
}
kvPairs = append(kvPairs, k+"="+strValue)
}
// 拼接参数
source := strings.Join(kvPairs, "&")
// 进行urlencode编码
source = strings.Replace(url.QueryEscape(source), "+", "%20", -1)
// 拼接AppSecret
source += "&" + appSecret
// 生成MD5签名
hash := md5.Sum([]byte(source))
sign := hex.EncodeToString(hash[:])
return sign
}
PHP
function sign($params, $appSecret)
{
// 1、按照健值从小到大排序
ksort($params);
// 2、键值对按照'='拼接,排除sign参数
$signParams = array();
foreach ($params as $key => $val) {
if ('sign' == $key) {
continue;
}
switch (gettype($val)) {
case 'NULL':
$val = '';
break;
case 'boolean':
$val = $val === true ? 'true' : 'false';
break;
case 'array':
$val = json_encode($val, JSON_UNESCAPED_UNICODE);
break;
default:
$val = (string) $val;
break;
}
array_push($signParams, $key . '=' . $val);
}
// 3、将上述数据按照&拼接
$signStr = join('&', $signParams);
// 4、将拼接结果用rawurlencode编码
$source = rawurlencode($signStr);
// 5、md5 hash计算签名
return md5($source . '&' . $appSecret);
}