
本文环境 CentOS8.0,PHP8.1,Nginx1.8,Workerman 4.0\ 不懂的可以评论或联系我邮箱:owen@owenzhang.com\ 著作权归OwenZhang所有。商业转载请联系OwenZhang获得授权,非商业转载请注明出处。

timestamp+nonce+sign 时间戳+随机数+签名参数接口验证
保证每次请求都不一样
sign签名机制
对参数进行签名,防止参数被非法篡改。sign一般是将所有非空参数按照升序排序然后+token+key+timestamp拼接在一起,然后使用加密算法进行加密,作为接口中的一个参数sign来传递,也可以将sign放到请求头中。
接口在网络传输过程中,参数值可以被修改,但是因为不了解sign计算方式,一般没法修改sign的值。当服务器调用接口前会按照sign的规则重新计算出sign的值然后和接口传递的sign参数的值做比较,如果相等表示参数值没有被篡改,如果不等,表示参数被非法篡改了。
如果sign在缓存服务器中因过期时间到了,而被删除了,此时当这个url再次请求服务器时,因token的过期时间和sign的过期时间一致,sign过期也意味着token过期,那样同样的url再访问服务器会因token错误会被拦截掉,这就是为什么sign和token的过期时间要保持一致的原因。


生成接口sign
将获得的signStr进行md5加密候转化成大写,再拼接密钥,再md5加密,并且转化为大写
签名生成的通用步骤如下:
第一步,设所有发送的数据非空参数值的参数按照参数名ASCII码从小到大排序(字典序),
使用URL键值对的格式(即key1:value1key2:value2…)拼接成字符串signStr。
特别注意以下重要规则:
◆ 参数名ASCII码从小到大排序(字典序);
◆ 如果参数的值为空不参与签名;
◆ 参数名区分大小写;
(((注意:不同环境后端框架md5结果不一样为,32/16位大小写,此处得前后端对应
https://www.sojson.com/md5/ 来猜是具体32/16位大小写 哪一种加密以下结果是 32位小写 md5加密的结果)))
将获得的signStr进行md5加密候转化成大写,再拼接密钥,再md5加密,并且转化为大写,密钥与后端商定假设传送的参数如下:
timestamp: 1651226218
nonce: cpNrX8wVBOhnIPTs
id: 1
name: zhang欧文
uuid: ffffffff-9252-a533-ffff-ffff81eff5b0
os_type: 3\app\middleware\VerifySign::class,//签名验证<?php
/**
* Programmer: Owen Zhang
* Email: owen@owenzhang.com
* Start Date: 05/15/22
* Last Update: 05 15, 2022 [OZ]
* Functions:
* 时间戳+随机数+签名参数接口验证
*/
namespace app\middleware;
use app\common\ApiStatus;
use support\Redis;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class VerifySign implements MiddlewareInterface
{
public function process(Request $request, callable $next): Response
{
//无需签名验证
if ($request->controller === 'app\api\v1\controller\White') return $next($request);
$timestamp = $request->post('timestamp');
$nonce = $request->post('nonce');
$sign = $request->post('sign');
$param = $request->post();
//时间戳校验
$res = $this->checkTime($timestamp);
if ($res['code'] != ApiStatus::API_SUCCESS->value) return json($res);
//随机数校验
$res = $this->checkNonce($nonce);
if ($res['code'] != ApiStatus::API_SUCCESS->value) return json($res);
//签名校验
$res = $this->checkSign($sign, $param);
if ($res['code'] != ApiStatus::API_SUCCESS->value) return json($res);
return $next($request);
}
//时间戳校验
protected function checkTime($timestamp)
{
if (empty($timestamp)) {
return ['code' => ApiStatus::API_ERROR->value, 'msg' => 'time error'];
}
//请求时间差是否合理值
if (abs(time() - $timestamp) > config('app.api_timeout')) {
return ['code' => ApiStatus::API_ERROR->value, 'msg' => 'request too fast'];
}
return ['code' => ApiStatus::API_SUCCESS->value, 'msg' => 'success'];
}
//随机数校验 10位随机数
protected function checkNonce($nonce)
{
if (empty($nonce)) {
return ['code' => ApiStatus::API_ERROR->value, 'msg' => 'random number error'];
}
if (strlen($nonce) > 16) {
return ['code' => ApiStatus::API_ERROR->value, 'msg' => 'random digit error'];
}
//删除过期的值
$end = time() - config('app.api_timeout');
Redis::zRemRangeByScore('nonce_list', '-inf', $end);
//查看nonce列表
$noncearr = Redis::zRangeByScore('nonce_list', '-inf', '+inf');
if (in_array($nonce, $noncearr)) {
return ['code' => ApiStatus::API_ERROR->value, 'msg' => 'random number repeat'];
}
//新标识随机数插入
Redis::zAdd('nonce_list', time(), $nonce);
return ['code' => ApiStatus::API_SUCCESS->value, 'msg' => 'success'];
}
//签名校验
protected function checkSign($sign, $param)
{
if (empty($sign)) {
return ['code' => ApiStatus::API_ERROR->value, 'msg' => 'signature error'];
}
unset($param['sign']);
$signLocal = $this->createSign($param);
if ($signLocal != $sign) {
return ['code' => ApiStatus::API_ERROR->value, 'msg' => 'illegal signature'];
}
return ['code' => ApiStatus::API_SUCCESS->value, 'msg' => 'success'];
}
/**
* 生成接口sign
* 将获得的signStr进行md5加密候转化成大写,再拼接密钥,再md5加密,并且转化为大写
* 签名生成的通用步骤如下:
*
* 第一步,设所有发送的数据非空参数值的参数按照参数名ASCII码从小到大排序(字典序),
* 使用URL键值对的格式(即key1:value1key2:value2…)拼接成字符串signStr。
*
* 特别注意以下重要规则:
*
* ◆ 参数名ASCII码从小到大排序(字典序);
* ◆ 如果参数的值为空不参与签名;
* ◆ 参数名区分大小写;
* 将获得的signStr进行md5加密候转化成大写,再拼接密钥,再md5加密,并且转化为大写,
* 密钥与后端商定
* 假设传送的参数如下:
*
* timestamp: 1651226218
* nonce: cpNrX8wVBOhnIPTs
* id: 1
* name: zhang欧文
* uuid: ffffffff-9252-a533-ffff-ffff81eff5b0
* os_type: 3
* 第一步:
* signStr="id1namezhang欧文noncecpNrX8wVBOhnIPTsos_type3timestamp1651226218uuidffffffff-9252-a533-ffff-ffff81eff5b0"
* 第二步:
* strtoupper(md5(strtoupper(md5(signStr)).密钥));
* 第三步:
* signStr=69AE2F1DDBE6D78699A087EE62F8FE63
*
* @return string signStr
*/
public function createSign($params)
{
$key = config('app.sign_salt');
$str = $this->createSignStr($params);
return strtoupper(md5(strtoupper(md5($str)) . $key));
}
/**
* 生成接口signStr
* 获取到的请求参数并按照参数名ASCII码从小到大排序,如果值是bool需转换成0,1表示,如果值是数组,需重新调用接口获取
*
* @return string signStr
*/
public function createSignStr($params)
{
ksort($params);
$sign = '';
foreach ($params as $key => $val) {
if ($val == null) continue;
if (is_bool($val)) $val = ($val) ? 1 : 0;
if (!$val) continue;
$sign .= $key . ((is_array($val)) ? $this->createSignStr($val) : $val);
}
return $sign;
}
}
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。