简介 应用基于http post或http get请求发送open api调用请求时,为了确保应用与rest服务器之间的安全通信,防止secret key盗用、数据篡改等恶意攻击行为,rest服务器使用了参数签名机制。应用在调用open api之前,需要为其所有请求参数计算一个md5签名,并追
简介应用基于http post或http get请求发送open api调用请求时,为了确保应用与rest服务器之间的安全通信,防止secret key盗用、数据篡改等恶意攻击行为,rest服务器使用了参数签名机制。应用在调用open api之前,需要为其所有请求参数计算一个md5签名,并追加到请求参数中,参数名为“sign”。rest服务器在接收到请求时会重新计算签名,并判断其值是否与应用传递过来的sign参数值一致,以此判定当前open api调用请求是否是被第三者伪造或篡改。
应用在调用open api之前需要通过 oauth2.0服务获得用户或平台的授权,获取到授权后将会拿到以下3个重要参数:
access_token:基于https调用open api时所需要的访问授权码;session_key:基于http调用open api时所需要的访问授权码;session_secret:基于http调用open api时计算参数签名用的签名密钥。其中,session_secret这个参数就是做参数签名时所需要的签名密钥。这与facebook、人人网等平台稍微有所区别,这两个平台在做参数签名时所用的签名密钥一般有2个:
如果是通过应用服务端调用open api,则注册应用时所拿到的应用密钥(即api key)就是参数签名密钥;如果是通过javascript、actionscript等客户端语言调用open api,则应用获取到用户授权后所拿到的session secret就是参数签名密钥。当然,通过服务端调用open api时也可以用session secret作为签名密钥。签名算法假设参与参数签名计算的请求参数分别是“k1”、“k2”、“k3”,它们的值分别是“v1”、“v2”、“v3”,则参数签名计算方法如下:
将请求参数格式化为“key=value”格式,即“k1=v1”、“k2=v2”、“k3=v3”;将格式化好的参数键值对以字典序升序排列后,拼接在一起,即“k1=v1k2=v2k3=v3”;在拼接好的字符串末尾追加上应用通过oauth2.0协议获取access token时所获取到的session_secret参数值;上述字符串的md5值即为签名的值。注意:计算签名时的请求参数中不要包含sign(签名)参数,因为sign参数的值此时还不知道,有待计算。
另外,计算签名的时候不需要对参数进行urlencode处理(“application/x-www-form-urlencoded”编码),但是发送请求的时候需要进行urlencode处理,这是很多开发者最容易犯错的地方。
签名过程示例假设某个应用需要获取某个uid为67411167的用户的基本资料,应用在之前的通过oauth2.0服务获取access token的过程中所拿到的session_key和session_secret参数值分别为:
session_key: 9xnnxe66zolsassjskd5gry9bin61iuei8ipjmjbwvu07rxp0j3c4gnhzr3gkhmha1a=session_secret: 27e1be4fdcaa83d7f61c489994ff6ed6调用open api时的系统时间(php中可以通过date('y-m-d h:i:s')来获取当前系统时间)为2011-06-21 17:18:09,希望rest服务器以json格式返回调用结果,即相当于参与参数签名计算的请求参数集合为:
[ session_key => 9xnnxe66zolsassjskd5gry9bin61iuei8ipjmjbwvu07rxp0j3c4gnhzr3gkhmha1a=, timestamp => 2011-06-21 17:18:09, format => json, uid => 67411167]
则计算签名的具体过程如下:
将请求参数格式化为“key=value”格式,格式化后的请求参数集合为:
[ session_key=9xnnxe66zolsassjskd5gry9bin61iuei8ipjmjbwvu07rxp0j3c4gnhzr3gkhmha1a=, timestamp=2011-06-21 17:18:09, format=json, uid=67411167 ]
将格式化好的参数键值对以字典序升序排列,得到如下参数集:
[ format=json, session_key=9xnnxe66zolsassjskd5gry9bin61iuei8ipjmjbwvu07rxp0j3c4gnhzr3gkhmha1a=, timestamp=2011-06-21 17:18:09, uid=67411167 ]
将前面排序好的参数集拼接在一起,得到如下字符串:format=jsonsession_key=9xnnxe66zolsassjskd5gry9bin61iuei8ipjmjbwvu07rxp0j3c4gnhzr3gkhmha1a=timestamp=2011-06-21 17:18:09uid=67411167
在拼接好的字符串末尾追加上应用通过oauth2.0协议获取access token时所获取到的session_secret参数值,得到如下字符串:format=jsonsession_key=9xnnxe66zolsassjskd5gry9bin61iuei8ipjmjbwvu07rxp0j3c4gnhzr3gkhmha1a=timestamp=2011-06-21 17:18:09uid=6741116727e1be4fdcaa83d7f61c489994ff6ed6
对前面得到的字符串求md5签名,得到的d24dd357a95a2579c410b3a92495f009就是调用api时所需要的sign参数值。接下来便可以通过http post方法或http get方法请求open api的rest服务器,进行接口调用了,如:
get /rest/2.0/passport/users/getinfo?session_key=9xnnxe66zolsassjskd5gry9bin61iuei8ipjmjbwvu07rxp0j3c4gnhzr3gkhmha1a%3d×tamp=2011-06-21+17%3a18%3a09&format=json&uid=67411167&sign=d24dd357a95a2579c410b3a92495f009 http/1.1host: openapi.baidu.comuser-agent: client of baidu open platformaccept: */*accept-encoding: gzip,deflateaccept-charset: utf-8connection: close或post /rest/2.0/passport/users/getinfo http/1.1host: openapi.baidu.comuser-agent: client of baidu open platformaccept: */*accept-encoding: gzip,deflateaccept-charset: utf-8content-length: 179connection: close session_key=9xnnxe66zolsassjskd5gry9bin61iuei8ipjmjbwvu07rxp0j3c4gnhzr3gkhmha1a%3d×tamp=2011-06-21+17%3a18%3a09&format=json&uid=67411167&sign=d24dd357a95a2579c410b3a92495f009
签名算法实现代码php代码实现获取签名的php代码实现方式如下所示:
/** * 签名生成算法 * @param array $params api调用的请求参数集合的关联数组,不包含sign参数 * @param string $secret 签名的密钥即获取access token时返回的session secret * @return string 返回参数签名值 */ function getsignature($params, $secret) { $str = ''; //待签名字符串 //先将参数以其参数名的字典序升序进行排序 ksort($params); //遍历排序后的参数数组中的每一个key/value对 foreach ($params as $k => $v) { //为key/value对生成一个key=value格式的字符串,并拼接到待签名字符串后面 $str .= $k=$v; } //将签名密钥拼接到签名字符串最后面 $str .= $secret; //通过md5算法为签名字符串生成一个md5签名,该签名就是我们要追加的sign参数值 return md5($str); }
调用示例:
$uid = 67411167;$params = array( session_key => 9xnnxe66zolsassjskd5gry9bin61iuei8ipjmjbwvu07rxp0j3c4gnhzr3gkhmha1a=, timestamp => 2011-06-21 17:18:09, format => json, uid => $uid,);$sign = getsignature($params, 27e1be4fdcaa83d7f61c489994ff6ed6);
java代码实现获取签名的java代码实现方式如下所示:
/** * 签名生成算法 * @param hashmap params 请求参数集,所有参数必须已转换为字符串类型 * @param string secret 签名密钥 * @return 签名 * @throws ioexception */public static string getsignature(hashmap params, string secret) throws ioexception{ // 先将参数以其参数名的字典序升序进行排序 map sortedparams = new treemap(params); set> entrys = sortedparams.entryset(); // 遍历排序后的字典,将所有参数按key=value格式拼接在一起 stringbuilder basestring = new stringbuilder(); for (entry param : entrys) { basestring.append(param.getkey()).append(=).append(param.getvalue()); } basestring.append(secret); // 使用md5对待签名串求签 byte[] bytes = null; try { messagedigest md5 = messagedigest.getinstance(md5); bytes = md5.digest(basestring.tostring().getbytes(utf-8)); } catch (generalsecurityexception ex) { throw new ioexception(ex); } // 将md5输出的二进制结果转换为小写的十六进制 stringbuilder sign = new stringbuilder(); for (int i = 0; i ) { string hex = integer.tohexstring(bytes[i] & 0xff); if (hex.length() == 1) { sign.append(0); } sign.append(hex); } return sign.tostring();}
注意:计算签名时所有参数的key和value都必须先转换为对应的字符串类型,因为在http请求中传递的内容都是字符串类型的,很多开发者都因为没注意到这点,直接将非字符串类型的参数的二进制值传递了进去,结果导致签名与服务端计算的不一致而出错。
c#代码实现获取签名的c#代码实现方式如下所示:
/// /// 计算参数签名/// /// 请求参数集,所有参数必须已转换为字符串类型/// 签名密钥/// 签名public static string getsignature(idictionarystring, string> parameters, string secret){ // 先将参数以其参数名的字典序升序进行排序 idictionarystring, string> sortedparams = new sorteddictionarystring, string>(parameters); ienumeratorstring, string>> iterator= sortedparams.getenumerator(); // 遍历排序后的字典,将所有参数按key=value格式拼接在一起 stringbuilder basestring= new stringbuilder(); while (iterator.movenext()) { string key = iterator.current.key; string value = iterator.current.value; if (!string.isnullorempty(key) && !string.isnullorempty(value)){ basestring.append(key).append(=).append(value); } } basestring.append(secret); // 使用md5对待签名串求签 md5 md5 = md5.create(); byte[] bytes = md5.computehash(encoding.utf8.getbytes(basestring.tostring())); // 将md5输出的二进制结果转换为小写的十六进制 stringbuilder result = new stringbuilder(); for (int i = 0; i ) { string hex = bytes[i].tostring(x); if (hex.length == 1) { result.append(0); } result.append(hex); } return result.tostring();}
服务器接受请求后,同样对参数进行签名,如果签名相同则数据没有被修改或者丢失。
注意:计算签名时所有参数的key和value都必须先转换为对应的字符串类型,因为在http请求中传递的内容都是字符串类型的,很多开发者都因为没注意到这点,直接将非字符串类型的参数的二进制值传递了进去,结果导致签名与服务端计算的不一致而出错。