参考: http://www.cnblogs.com/codelir/p/5327462.html
签名的主要作用
- 请求来源(身份)是否合法?
- 请求的唯一性(不可重复请求)
步骤
- 服务端给客户端分配
app_id
app_secret
- 客户端和服务端通过指定的算法生成签名
sign
- 客户端每次请求都把
app_id
和生成好的sign
放到请求参数中传递给服务端 - 服务端拿出客户端传递的
app_id 对应的 app_secret
通过算法生成一个sign
和客户端传递的sign
对比
注意: app_secret
只是为了生成 sign
用的,请不要将 app_secret
放到请求参数中
关于算法
- 将所有请求参数加入到算法中
- 将时间戳加入到算法中(注意: js获取的时间戳和PHP获取的时间戳格式不一致)
- 将
app_secret
加入到算法中
举个例子, 此处以 jquery 和 laravel 为例
使用 axios
的话,原理也是一样的
- 客户端代码
<script src="jquery.min.js"></script>
<script>
$(function () {
// 请求参数
var params = {
id: 1,
app_id: 'xKYf550dzriIVeZbrcz2WpAMV0SOMCelpUcNfLDWm9vf1BdsT41uvqVE3PLH7lQx'
};
/**
* 生成签名加密算法
* @param $params
*/
function signGenerator($params) {
// 获取秘钥和请求参数循环组成: secret:key=value&key=value×tamp
var secret = 'fw2FZjjmvKlpoByizWfScZbAluXdNwooABu93LEdKRV5ZOtJKrL7LEosb4IGBw4W';
var sign = secret + ':';
for (var key in params) {
sign += key + '=' + $params[key] + '&';
}
// 获取PHP格式的时间戳
var timestamp = (new Date()).getTime() / 1000;
console.log("客户端生成的签名:", window.btoa(sign + Math.floor(timestamp)));
// window.btoa是js内置的base64加密算法
return window.btoa(sign + Math.floor(timestamp));
}
// 发送请求
$.ajax({
type: "GET",
url: "https://www.example.com/api/test",
data: params,
beforeSend: function (request) {
// 将签名添加到请求头中
request.setRequestHeader("sign", signGenerator(params));
},
success: function (response) {
console.log(JSON.stringify(response));
}
});
});
</script>
- 服务端代码
此处使用的是laravel的中间件
<?php
namespace App\Http\Middleware;
use Closure;
/**
* 签名验证中间件
* Class SignVerify
* @package App\Http\Middleware
*/
class SignVerify
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
// 此处的 signList 是所有允许的 app_id 和 app_secret
// 正常情况下应该是存放到数据库
// 我写死放到这里, 只是为了演示效果
$signList = [
[
'app_id' => 'xKYf550dzriIVeZbrcz2WpAMV0SOMCelpUcNfLDWm9vf1BdsT41uvqVE3PLH7lQx',
'app_secret' => 'fw2FZjjmvKlpoByizWfScZbAluXdNwooABu93LEdKRV5ZOtJKrL7LEosb4IGBw4W'
],
//...
[
'app_id' => 'aaaaaaaaaa',
'app_secret' => 'bbbbbbbbbb'
]
];
// 获取客户端传递的 app_id 匹配服务端的 app_id对应的 值
$client_app_id = $request->input('app_id');
$server_app_secret = null;
foreach ($signList as $key => $value) {
if ($value['app_id'] == $client_app_id) {
$server_app_secret = $value;
break;
}
continue;
}
if (is_null($server_app_secret)) {
return 'Signature verification failed';
}
// 获取 header 中客户端传递的 sign 然后, 和服务端生成的sign对比
$client_sign = $request->header('sign');
// 服务端生成签名(循环生成, 如果直接使用当前时间戳可能有网络延迟问题)
$time = time();
$params = $request->all();
$expire = 3; // 3内秒钟有效, 可以将这个放到配置文件中
for ($i = 0; $i < $expire; $i++) {
// 签名正确就往后执行
if ($this->signGenerator($params, $server_app_secret, $time + $i) == $client_sign) {
return $next($request);
}
continue;
}
return 'Signature verification failed';
}
/**
* 生成签名
*
* @param $params 请求参数
* @param $secret 秘钥
* @param $time 时间戳
* @return string
*/
public function signGenerator($params, $secret, $timestamps)
{
$secret .= ':';
foreach ($params as $key => $value) {
$secret .= $key . '=' . '&';
}
// base64
return base64_encode($secret . $timestamps);
}
}
网友评论