简介

应用基于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&timestamp=2011-06-21+17%3A18%3A09&format=json&uid=67411167&sign=d24dd357a95a2579c410b3a92495f009 HTTP/1.1
Host: openapi.baidu.com
User-Agent: Client of Baidu Open Platform
Accept: */*
Accept-Encoding: gzip,deflate
Accept-Charset: utf-8
Connection: close 或
POST /rest/2.0/passport/users/getInfo HTTP/1.1
Host: openapi.baidu.com
User-Agent: Client of Baidu Open Platform
Accept: */*
Accept-Encoding: gzip,deflate
Accept-Charset: utf-8
Content-Length: 179
Connection: close session_key=9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A%3D&timestamp=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<String,String> params 请求参数集,所有参数必须已转换为字符串类型
* @param String secret 签名密钥
* @return 签名
* @throws IOException
*/
public static String getSignature(HashMap<String,String> params, String secret) throws IOException
{
// 先将参数以其参数名的字典序升序进行排序
Map<String, String> sortedParams = new TreeMap<String, String>(params);
Set<Entry<String, String>> entrys = sortedParams.entrySet(); // 遍历排序后的字典,将所有参数按"key=value"格式拼接在一起
StringBuilder basestring = new StringBuilder();
for (Entry<String, String> 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 < bytes.length; 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#代码实现方式如下所示:


/// <summary>
/// 计算参数签名
/// </summary>
/// <param name="params">请求参数集,所有参数必须已转换为字符串类型</param>
/// <param name="secret">签名密钥</param>
/// <returns>签名</returns>
public static string getSignature(IDictionary<string, string> parameters, string secret)
{
// 先将参数以其参数名的字典序升序进行排序
IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters);
IEnumerator<KeyValuePair<string, 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 < bytes.Length; i++) {
string hex = bytes[i].ToString("x");
if (hex.Length == 1) {
result.Append("0");
}
result.Append(hex);
} return result.ToString();
}

服务器接受请求后,同样对参数进行签名,如果签名相同则数据没有被修改或者丢失。


注意:计算签名时所有参数的key和value都必须先转换为对应的字符串类型,因为在HTTP请求中传递的内容都是字符串类型的,很多开发者都因为没注意到这点,直接将非字符串类型的参数的二进制值传递了进去,结果导致签名与服务端计算的不一致而出错。

原文:http://www.cnblogs.com/taoweiji/p/3902545.html

PHP、Java、C#实现URI参数签名算法,确保应用与REST服务器之间的安全通信,防止Secret Key盗用、数据篡改等恶意攻击行为的更多相关文章

  1. URI参数签名算法

    简介 应用基于HTTP POST或HTTP GET请求发送Open API调用请求时,为了确保应用与百度REST服务器之间的安全通信,防止Secret Key盗用.数据篡改等恶意攻击行为,百度REST ...

  2. Java网络编程-URI和URL

    前提 前面的一篇文章<Java中的Internet查询>分析完了如何通过IP地址或者主机名确定主机在因特网中的地址.任意给定主机上可能会有任意多个资源,这些资源需要有标识符方便主机之间访问 ...

  3. Java:方法的参数是传值还是传引用

    Java中方法的参数总是采用传值的方式. 下列方法欲实现对象的交换,但实际上是不能实现的. public void swap(simpleClass a,simpleClass b){ simpleC ...

  4. Java中的可变参数以及foreach语句

    Java中的可变参数的定义格式如下: 返回值类型  方法名称(类型 ... 参数名称){} foreach语句的格式如下: for ( 数据类型  变量名称 :数据名称){ ... } public ...

  5. Java 数组 可变长参数 实例

    可以把类型相同但个数可变的参数传递给方法,方法中的参数声明如下: typeName...parameterName (类型名...参数名) 在方法声明中,指定类型后紧跟着省略号...,只能给方法指定一 ...

  6. Java调用.Net WebService参数为空解决办法 (远程)调试webservice方法 转

    Java调用.Net WebService参数为空解决办法 (远程)调试webservice方法   同事遇到一个很囧的问题,java调,netwebservice的时候,调用无参数方法成功,调用有参 ...

  7. java中的中文参数存到数据库乱码问题

    关于java中的中文参数乱码问题,遇见过很多,若开发工具的字符集环境和数据库的字符集环境都一样,存到数据库中还是乱码的话,可以通过以下方法解决: 用数据库客户端检查每个字段的字符集和字符集校对和这个表 ...

  8. java中可变长参数

    ** * Created by Lenovo on 2017/12/10. * java中可变长参数 */ public class reflect04 { //m1有一个int类型的可比变长参数 / ...

  9. JAVA方法中的参数用final来修饰的原因

    JAVA方法中的参数用final来修饰的原因   很多人都说在JAVA中用final来修饰方法参数的原因是防止方法参数在调用时被篡改,其实也就是这个原因,但理解起来可能会有歧义,有的人认为是调用语句的 ...

随机推荐

  1. MySQL内存----使用说明全局缓存+线程缓存) 转

    MySQL内存使用说明(全局缓存+线程缓存) 首先我们来看一个公式,MySQL中内存分为全局内存和线程内存两大部分(其实并不全部,只是影响比较大的 部分): per_thread_buffers=(r ...

  2. TFS服务器(微软源代码管理服务器)上彻底删除项目

    在TFS服务器上建立了很多项目,发现在Team Explorer中,只能移除团队项目,这种移除,只是将项目从当前Team Explorer项目列表中删除,下一次Connect到TFS服务器时,或者刷新 ...

  3. Python_爬虫3

    正则表达式 在前面我们已经搞定了怎样获取页面的内容,不过还差一步,这么多杂乱的代码夹杂文字我们怎样把它提取出来整理呢?下面就开始介绍一个十分强大的工具,正则表达式! 1.了解正则表达式 正则表达式是对 ...

  4. C#_uploadify_mvc_version

    jQuery Uploadify在ASP.NET MVC3中的使用 1.Uploadify简介 Uploadify是基于jQuery的一种上传插件,支持多文件.带进度条显示上传,在项目开发中常被使用. ...

  5. Mac上mariadb的启动与关闭

    1. 启动 launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mariadb.plist 2. 关闭 launchctl unload ~/Lib ...

  6. IE Firefox Safari 下 通过Div“隐藏”设置Accesskey的submit input

    实现效果: 进入页面后 通过快捷键  如Alt+C 调用相关的隐藏按钮 实现功能操作 正常情况下 设置 button => input type="submit" acces ...

  7. hdu 4669 动态规划

    思路:主要就是一个动态方程dp[now][(j*Exp[len[num[i]]]+num[i])%k]+=dp[pre][j];我用的是滚动数组.其实也就是dp[i][(j*Exp[len[num[i ...

  8. mac 如何进入/usr/sbin目录

    1.进入terminal, 输入 ls /usr/sbin 2.在finder>前往文件夹,输入路径/usr/sbin

  9. 原生javascript焦点轮播图

    刚刚学会,写了一个轮播图效果,不过bug蛮多,请高手指点一下,谢谢 <!DOCTYPE html> <html> <head> <meta charset=& ...

  10. C#中的DateTime:本周第一天,本月第一天,今年第一天,本周第一天的时间

    有时辰需要按照当前时刻,断定其它的都没有什么难度,只是本季度稍稍麻烦些.因为一年有四个季度,可以按照当前月份,获得本季度第一个月的月份,然后这个月的第一天,就是本季度的第一天了 DateTime dt ...