參考: http://www.cnblogs.com/codelir/p/5327462.html
簽名的主要作用
- 請(qǐng)求來源(身份)是否合法?
- 請(qǐng)求的唯一性(不可重復(fù)請(qǐng)求)
步驟
- 服務(wù)端給客戶端分配
app_id
app_secret
- 客戶端和服務(wù)端通過指定的算法生成簽名
sign
- 客戶端每次請(qǐng)求都把
app_id
和生成好的sign
放到請(qǐng)求參數(shù)中傳遞給服務(wù)端 - 服務(wù)端拿出客戶端傳遞的
app_id 對(duì)應(yīng)的 app_secret
通過算法生成一個(gè)sign
和客戶端傳遞的sign
對(duì)比
注意: app_secret
只是為了生成 sign
用的,請(qǐng)不要將 app_secret
放到請(qǐng)求參數(shù)中
關(guān)于算法
- 將所有請(qǐng)求參數(shù)加入到算法中
- 將時(shí)間戳加入到算法中(注意: js獲取的時(shí)間戳和PHP獲取的時(shí)間戳格式不一致)
- 將
app_secret
加入到算法中
舉個(gè)例子, 此處以 jquery 和 laravel 為例
使用 axios
的話,原理也是一樣的
- 客戶端代碼
<script src="jquery.min.js"></script>
<script>
$(function () {
// 請(qǐng)求參數(shù)
var params = {
id: 1,
app_id: 'xKYf550dzriIVeZbrcz2WpAMV0SOMCelpUcNfLDWm9vf1BdsT41uvqVE3PLH7lQx'
};
/**
* 生成簽名加密算法
* @param $params
*/
function signGenerator($params) {
// 獲取秘鑰和請(qǐng)求參數(shù)循環(huán)組成: secret:key=value&key=value×tamp
var secret = 'fw2FZjjmvKlpoByizWfScZbAluXdNwooABu93LEdKRV5ZOtJKrL7LEosb4IGBw4W';
var sign = secret + ':';
for (var key in params) {
sign += key + '=' + $params[key] + '&';
}
// 獲取PHP格式的時(shí)間戳
var timestamp = (new Date()).getTime() / 1000;
console.log("客戶端生成的簽名:", window.btoa(sign + Math.floor(timestamp)));
// window.btoa是js內(nèi)置的base64加密算法
return window.btoa(sign + Math.floor(timestamp));
}
// 發(fā)送請(qǐng)求
$.ajax({
type: "GET",
url: "https://www.example.com/api/test",
data: params,
beforeSend: function (request) {
// 將簽名添加到請(qǐng)求頭中
request.setRequestHeader("sign", signGenerator(params));
},
success: function (response) {
console.log(JSON.stringify(response));
}
});
});
</script>
- 服務(wù)端代碼
此處使用的是laravel的中間件
<?php
namespace App\Http\Middleware;
use Closure;
/**
* 簽名驗(yàn)證中間件
* 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
// 正常情況下應(yīng)該是存放到數(shù)據(jù)庫
// 我寫死放到這里, 只是為了演示效果
$signList = [
[
'app_id' => 'xKYf550dzriIVeZbrcz2WpAMV0SOMCelpUcNfLDWm9vf1BdsT41uvqVE3PLH7lQx',
'app_secret' => 'fw2FZjjmvKlpoByizWfScZbAluXdNwooABu93LEdKRV5ZOtJKrL7LEosb4IGBw4W'
],
//...
[
'app_id' => 'aaaaaaaaaa',
'app_secret' => 'bbbbbbbbbb'
]
];
// 獲取客戶端傳遞的 app_id 匹配服務(wù)端的 app_id對(duì)應(yīng)的 值
$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 然后, 和服務(wù)端生成的sign對(duì)比
$client_sign = $request->header('sign');
// 服務(wù)端生成簽名(循環(huán)生成, 如果直接使用當(dāng)前時(shí)間戳可能有網(wǎng)絡(luò)延遲問題)
$time = time();
$params = $request->all();
$expire = 3; // 3內(nèi)秒鐘有效, 可以將這個(gè)放到配置文件中
for ($i = 0; $i < $expire; $i++) {
// 簽名正確就往后執(zhí)行
if ($this->signGenerator($params, $server_app_secret, $time + $i) == $client_sign) {
return $next($request);
}
continue;
}
return 'Signature verification failed';
}
/**
* 生成簽名
*
* @param $params 請(qǐng)求參數(shù)
* @param $secret 秘鑰
* @param $time 時(shí)間戳
* @return string
*/
public function signGenerator($params, $secret, $timestamps)
{
$secret .= ':';
foreach ($params as $key => $value) {
$secret .= $key . '=' . '&';
}
// base64
return base64_encode($secret . $timestamps);
}
}