首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >workerman 签名验证中间件 timestamp+nonce+sign 时间戳 + 随机数 + 签名参数接口验证

workerman 签名验证中间件 timestamp+nonce+sign 时间戳 + 随机数 + 签名参数接口验证

原创
作者头像
OwenZhang
发布2022-05-30 14:38:11
发布2022-05-30 14:38:11
2.6K0
举报
文章被收录于专栏:Owen's WorldOwen's World

本文环境 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的过期时间要保持一致的原因。

image.png
image.png
image.png
image.png

sign签名的设置规则

生成接口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加密,并且转化为大写,密钥与后端商定假设传送的参数如下:

代码语言:txt
复制
    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
  • 第四步:返回sign return string signStr

代码实例

config/middleware.php

代码语言:txt
复制
\app\middleware\VerifySign::class,//签名验证

app/middleware/VerifySign.php

代码语言:txt
复制
<?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;
    }
}

运行状态

Buy me a cup of coffee :)

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简要
  • sign签名的设置规则
  • 代码实例
    • config/middleware.php
    • app/middleware/VerifySign.php
  • 运行状态
  • Buy me a cup of coffee :)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档