PHP、Java、C#实现URI参数签名算法,保准应用与REST服务器之间的安全通信,防止Secret Key盗用、数据篡改等恶意攻击行为
PHP、Java、C#实现URI参数签名算法,确保应用与REST服务器之间的安全通信,防止Secret Key盗用、数据篡改等恶意攻击行为
简介
应用基于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格式返回调用结果,即相当于参与参数签名计算的请求参数集合为:
<span style="color: #000000;">[ </span>"session_key" => "9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A="<span style="color: #000000;">, </span>"timestamp" => "2011-06-21 17:18:09"<span style="color: #000000;">, </span>"format" => "json"<span style="color: #000000;">, </span>"uid" => 67411167<span style="color: #000000;">]</span>
则计算签名的具体过程如下:
- 将请求参数格式化为“key=value”格式,格式化后的请求参数集合为:
<span style="color: #000000;"> [ </span>"session_key=9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A="<span style="color: #000000;">, </span>"timestamp=2011-06-21 17:18:09"<span style="color: #000000;">, </span>"format=json"<span style="color: #000000;">, </span>"uid=67411167"<span style="color: #000000;"> ]</span>
- 将格式化好的参数键值对以字典序升序排列,得到如下参数集:
<span style="color: #000000;"> [ </span>"format=json"<span style="color: #000000;">, </span>"session_key=9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A="<span style="color: #000000;">, </span>"timestamp=2011-06-21 17:18:09"<span style="color: #000000;">, </span>"uid=67411167"<span style="color: #000000;"> ]</span>
- 将前面排序好的参数集拼接在一起,得到如下字符串:
format<span style="color: #339933;">=</span>jsonsession_key<span style="color: #339933;">=</span>9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A<span style="color: #339933;">=</span>timestamp<span style="color: #339933;">=</span><span style="color: #cc66cc;">2011</span><span style="color: #339933;">-</span><span style="color: #208080;">06</span><span style="color: #339933;">-</span><span style="color: #cc66cc;">21</span> <span style="color: #cc66cc;">17</span><span style="color: #339933;">:</span><span style="color: #cc66cc;">18</span><span style="color: #339933;">:</span>09uid<span style="color: #339933;">=</span><span style="color: #cc66cc;">67411167</span>
- 在拼接好的字符串末尾追加上应用通过OAuth2.0协议获取Access Token时所获取到的session_secret参数值,得到如下字符串:
format<span style="color: #339933;">=</span>jsonsession_key<span style="color: #339933;">=</span>9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A<span style="color: #339933;">=</span>timestamp<span style="color: #339933;">=</span><span style="color: #cc66cc;">2011</span><span style="color: #339933;">-</span><span style="color: #208080;">06</span><span style="color: #339933;">-</span><span style="color: #cc66cc;">21</span> <span style="color: #cc66cc;">17</span><span style="color: #339933;">:</span><span style="color: #cc66cc;">18</span><span style="color: #339933;">:</span>09uid<span style="color: #339933;">=</span>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.1<span style="color: #000000;">Host: openapi.baidu.comUser</span>-<span style="color: #000000;">Agent: Client of Baidu Open PlatformAccept: </span>*<span style="color: #008000;">/*</span><span style="color: #008000;">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: </span><span style="color: #008000;">*/</span>*<span style="color: #000000;">Accept</span>-<span style="color: #000000;">Encoding: gzip,deflateAccept</span>-Charset: utf-8<span style="color: #000000;">Content</span>-Length: 179<span style="color: #000000;">Connection: close session_key</span>=9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A%3D×tamp=2011-06-21+17%3A18%3A09&format=json&uid=67411167&sign=d24dd357a95a2579c410b3a92495f009
签名算法实现代码
PHP代码实现
获取签名的PHP代码实现方式如下所示:
<span style="color: #008000;">/*</span><span style="color: #008000;">* * 签名生成算法 * @param array $params API调用的请求参数集合的关联数组,不包含sign参数 * @param string $secret 签名的密钥即获取access token时返回的session secret * @return string 返回参数签名值 </span><span style="color: #008000;">*/</span> <span style="color: #0000ff;">function</span> getSignature(<span style="color: #800080;">$params</span>, <span style="color: #800080;">$secret</span><span style="color: #000000;">) { </span><span style="color: #800080;">$str</span> = ''; <span style="color: #008000;">//</span><span style="color: #008000;">待签名字符串 //先将参数以其参数名的字典序升序进行排序</span> <span style="color: #008080;">ksort</span>(<span style="color: #800080;">$params</span><span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;">遍历排序后的参数数组中的每一个key/value对</span> <span style="color: #0000ff;">foreach</span> (<span style="color: #800080;">$params</span> <span style="color: #0000ff;">as</span> <span style="color: #800080;">$k</span> => <span style="color: #800080;">$v</span><span style="color: #000000;">) { </span><span style="color: #008000;">//</span><span style="color: #008000;">为key/value对生成一个key=value格式的字符串,并拼接到待签名字符串后面</span> <span style="color: #800080;">$str</span> .= "<span style="color: #800080;">$k</span>=<span style="color: #800080;">$v</span>"<span style="color: #000000;">; } </span><span style="color: #008000;">//</span><span style="color: #008000;">将签名密钥拼接到签名字符串最后面</span> <span style="color: #800080;">$str</span> .= <span style="color: #800080;">$secret</span><span style="color: #000000;">; </span><span style="color: #008000;">//</span><span style="color: #008000;">通过md5算法为签名字符串生成一个md5签名,该签名就是我们要追加的sign参数值</span> <span style="color: #0000ff;">return</span> <span style="color: #008080;">md5</span>(<span style="color: #800080;">$str</span><span style="color: #000000;">); }</span>
调用示例:
<span style="color: #800080;">$uid</span> = 67411167<span style="color: #000000;">;</span><span style="color: #800080;">$params</span> = <span style="color: #0000ff;">array</span><span style="color: #000000;">( </span>"session_key" => "9XNNXe66zOlSassjSKD5gry9BiN61IUEi8IpJmjBwvU07RXP0J3c4GnhZR3GKhMHa1A=", "timestamp" => "2011-06-21 17:18:09", "format" => "json", "uid" => <span style="color: #800080;">$uid</span>,<span style="color: #000000;">);</span><span style="color: #800080;">$sign</span> = getSignature(<span style="color: #800080;">$params</span>, "27e1be4fdcaa83d7f61c489994ff6ed6");
Java代码实现
获取签名的java代码实现方式如下所示:
<span style="color: #008000;">/**</span><span style="color: #008000;"> * 签名生成算法 * </span><span style="color: #808080;">@param</span><span style="color: #008000;"> HashMap<String,String> params 请求参数集,所有参数必须已转换为字符串类型 * </span><span style="color: #808080;">@param</span><span style="color: #008000;"> String secret 签名密钥 * </span><span style="color: #808080;">@return</span><span style="color: #008000;"> 签名 * </span><span style="color: #808080;">@throws</span><span style="color: #008000;"> IOException </span><span style="color: #008000;">*/</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> String getSignature(HashMap<String,String> params, String secret) <span style="color: #0000ff;">throws</span><span style="color: #000000;"> IOException{ </span><span style="color: #008000;">//</span><span style="color: #008000;"> 先将参数以其参数名的字典序升序进行排序</span> Map<String, String> sortedParams = <span style="color: #0000ff;">new</span> TreeMap<String, String><span style="color: #000000;">(params); Set</span><Entry<String, String>> entrys =<span style="color: #000000;"> sortedParams.entrySet(); </span><span style="color: #008000;">//</span><span style="color: #008000;"> 遍历排序后的字典,将所有参数按"key=value"格式拼接在一起</span> StringBuilder basestring = <span style="color: #0000ff;">new</span><span style="color: #000000;"> StringBuilder(); </span><span style="color: #0000ff;">for</span> (Entry<String, String><span style="color: #000000;"> param : entrys) { basestring.append(param.getKey()).append(</span>"="<span style="color: #000000;">).append(param.getValue()); } basestring.append(secret); </span><span style="color: #008000;">//</span><span style="color: #008000;"> 使用MD5对待签名串求签</span> <span style="color: #0000ff;">byte</span>[] bytes = <span style="color: #0000ff;">null</span><span style="color: #000000;">; </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> { MessageDigest md5 </span>= MessageDigest.getInstance("MD5"<span style="color: #000000;">); bytes </span>= md5.digest(basestring.toString().getBytes("UTF-8"<span style="color: #000000;">)); } </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (GeneralSecurityException ex) { </span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span><span style="color: #000000;"> IOException(ex); } </span><span style="color: #008000;">//</span><span style="color: #008000;"> 将MD5输出的二进制结果转换为小写的十六进制</span> StringBuilder sign = <span style="color: #0000ff;">new</span><span style="color: #000000;"> StringBuilder(); </span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = 0; i < bytes.length; i++<span style="color: #000000;">) { String hex </span>= Integer.toHexString(bytes[i] & 0xFF<span style="color: #000000;">); </span><span style="color: #0000ff;">if</span> (hex.length() == 1<span style="color: #000000;">) { sign.append(</span>"0"<span style="color: #000000;">); } sign.append(hex); } </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> sign.toString();}</span>
注意:计算签名时所有参数的key和value都必须先转换为对应的字符串类型,因为在HTTP请求中传递的内容都是字符串类型的,很多开发者都因为没注意到这点,直接将非字符串类型的参数的二进制值传递了进去,结果导致签名与服务端计算的不一致而出错。
C#代码实现
获取签名的C#代码实现方式如下所示:
<span style="color: #808080;">///</span> <span style="color: #808080;"><summary></span><span style="color: #808080;">///</span><span style="color: #008000;"> 计算参数签名</span><span style="color: #808080;">///</span> <span style="color: #808080;"></summary></span><span style="color: #808080;">///</span> <span style="color: #808080;"><param name="params"></span><span style="color: #008000;">请求参数集,所有参数必须已转换为字符串类型</span><span style="color: #808080;"></param></span><span style="color: #808080;">///</span> <span style="color: #808080;"><param name="secret"></span><span style="color: #008000;">签名密钥</span><span style="color: #808080;"></param></span><span style="color: #808080;">///</span> <span style="color: #808080;"><returns></span><span style="color: #008000;">签名</span><span style="color: #808080;"></returns></span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">string</span> getSignature(IDictionary<<span style="color: #0000ff;">string</span>, <span style="color: #0000ff;">string</span>> parameters, <span style="color: #0000ff;">string</span><span style="color: #000000;"> secret){ </span><span style="color: #008000;">//</span><span style="color: #008000;"> 先将参数以其参数名的字典序升序进行排序</span> IDictionary<<span style="color: #0000ff;">string</span>, <span style="color: #0000ff;">string</span>> sortedParams = <span style="color: #0000ff;">new</span> SortedDictionary<<span style="color: #0000ff;">string</span>, <span style="color: #0000ff;">string</span>><span style="color: #000000;">(parameters); IEnumerator</span><KeyValuePair<<span style="color: #0000ff;">string</span>, <span style="color: #0000ff;">string</span>>> iterator=<span style="color: #000000;"> sortedParams.GetEnumerator(); </span><span style="color: #008000;">//</span><span style="color: #008000;"> 遍历排序后的字典,将所有参数按"key=value"格式拼接在一起</span> StringBuilder basestring= <span style="color: #0000ff;">new</span><span style="color: #000000;"> StringBuilder(); </span><span style="color: #0000ff;">while</span><span style="color: #000000;"> (iterator.MoveNext()) { </span><span style="color: #0000ff;">string</span> key =<span style="color: #000000;"> iterator.Current.Key; </span><span style="color: #0000ff;">string</span> value =<span style="color: #000000;"> iterator.Current.Value; </span><span style="color: #0000ff;">if</span> (!<span style="color: #0000ff;">string</span>.IsNullOrEmpty(key) && !<span style="color: #0000ff;">string</span><span style="color: #000000;">.IsNullOrEmpty(value)){ basestring.Append(key).Append(</span><span style="color: #800000;">"</span><span style="color: #800000;">=</span><span style="color: #800000;">"</span><span style="color: #000000;">).Append(value); } } basestring.Append(secret); </span><span style="color: #008000;">//</span><span style="color: #008000;"> 使用MD5对待签名串求签</span> MD5 md5 =<span style="color: #000000;"> MD5.Create(); </span><span style="color: #0000ff;">byte</span>[] bytes =<span style="color: #000000;"> md5.ComputeHash(Encoding.UTF8.GetBytes(basestring.ToString())); </span><span style="color: #008000;">//</span><span style="color: #008000;"> 将MD5输出的二进制结果转换为小写的十六进制</span> StringBuilder result = <span style="color: #0000ff;">new</span><span style="color: #000000;"> StringBuilder(); </span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = <span style="color: #800080;">0</span>; i < bytes.Length; i++<span style="color: #000000;">) { </span><span style="color: #0000ff;">string</span> hex = bytes[i].ToString(<span style="color: #800000;">"</span><span style="color: #800000;">x</span><span style="color: #800000;">"</span><span style="color: #000000;">); </span><span style="color: #0000ff;">if</span> (hex.Length == <span style="color: #800080;">1</span><span style="color: #000000;">) { result.Append(</span><span style="color: #800000;">"</span><span style="color: #800000;">0</span><span style="color: #800000;">"</span><span style="color: #000000;">); } result.Append(hex); } </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> result.ToString();}</span>
服务器接受请求后,同样对参数进行签名,如果签名相同则数据没有被修改或者丢失。
注意:计算签名时所有参数的key和value都必须先转换为对应的字符串类型,因为在HTTP请求中传递的内容都是字符串类型的,很多开发者都因为没注意到这点,直接将非字符串类型的参数的二进制值传递了进去,结果导致签名与服务端计算的不一致而出错。

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

使用Java的String.valueOf()函数将基本数据类型转换为字符串在Java开发中,当我们需要将基本数据类型转换为字符串时,一种常见的方法是使用String类的valueOf()函数。这个函数可以接受基本数据类型的参数,并返回对应的字符串表示。在本文中,我们将探讨如何使用String.valueOf()函数进行基本数据类型转换,并提供一些代码示例来

SQL中的Identity是什么,需要具体代码示例在SQL中,Identity是一种用于生成自增数字的特殊数据类型,它常用于唯一标识表中的每一行数据。Identity列通常与主键列配合使用,可以确保每条记录都有一个独一无二的标识符。本文将详细介绍Identity的使用方式以及一些实际的代码示例。Identity的基本使用方式在创建表时,可以使用Identit

char数组转string的方法:可以通过赋值来实现,使用{char a[]=" abc d\0efg ";string s=a;}语法,让char数组对string直接赋值,执行代码即可完成转换。

session失效通常是由于 session 的生存时间过期或者服务器关闭导致的。其解决办法:1、延长session的生存时间;2、使用持久化存储;3、使用cookie;4、异步更新session;5、使用会话管理中间件。

一、功能概览键空间通知使得客户端可以通过订阅频道或模式,来接收那些以某种方式改动了Rediskey变化的事件。所有修改key键的命令。所有接收到LPUSHkeyvalue[value…]命令的键。db数据库中所有已过期的键。事件通过Redis的订阅与发布功能(pub/sub)来进行分发,因此所有支持订阅与发布功能的客户端都可以在无须做任何修改的情况下,直接使用键空间通知功能。因为Redis目前的订阅与发布功能采取的是发送即忘(fireandforget)策略,所以如果你的程

问题:今天项目中遇到了一个设置时间超时的问题,按SpringBoot2的application.properties更改一直不生效。解决方案:server.*属性用于控制SpringBoot使用的嵌入式容器。SpringBoot将使用ServletWebServerFactory实例之一创建servlet容器的实例。这些类使用server.*属性来配置受控的servlet容器(tomcat,jetty等)。当应用程序作为war文件部署到Tomcat实例时,server.*属性不适用。它们不适用,

PHPSession跨域问题的解决方法在前后端分离的开发中,跨域请求已成为常态。在处理跨域问题时,我们通常会涉及到session的使用和管理。然而,由于浏览器的同源策略限制,跨域情况下默认情况下无法共享session。为了解决这个问题,我们需要采用一些技巧和方法来实现session的跨域共享。一、使用cookie跨域共享session最常

1.基于session实现短信登录1.1短信登录流程图1.2实现发送短信验证码前端请求说明:说明请求方式POST请求路径/user/code请求参数phone(电话号码)返回值无后端接口实现:@Slf4j@ServicepublicclassUserServiceImplextendsServiceImplimplementsIUserService{@OverridepublicResultsendCode(Stringphone,HttpSessionsession){//1.校验手机号if
