其他分享
首页 > 其他分享> > api 签名流程 API接口签名验证

api 签名流程 API接口签名验证

作者:互联网

现在越来越多的公司以 API 的形式对外提供服务,这些 API 接口大多暴露在公网上,所以安全性就变的很重要了。最直接的风险如下:

因此需要设计一些接口安全保护的方式来增强接口安全,在运输层可添加 SSL 证书,上 HTTPS,在应用层主要是通过一些加密逻辑来实现。目前主流的两种是在 HTTP Header 里加认证信息和 API 签名。

大概的思路是在请求包带上我们自己构造好的签名,这个签名必须满足下面几点:
a、唯一性,签名是唯一的,可验证目标用户
b、可变性,每次携带的签名必须是变化的
c、时效性,具有一定的时效,过期作废
d、完整性,能够对数据包进行验证,防止篡改

 

请求身份

为开发者分配SecretKey(用于接口加密,确保不易被穷举,生成算法不易被猜测)。

 

请求端构造签名串

  

// 按首字母排序
function objKeySort(obj) { //排序的函数
    var newkey = Object.keys(obj).sort();
    //先用Object内置类的keys方法获取要排序对象的属性名,再利用Array原型上的sort方法对获取的属性名进行排序,newkey是一个数组
    var newObj = {}; //创建一个新的对象,用于存放排好序的键值对
    let str = ''
    for (var i = 0; i < newkey.length; i++) { //遍历newkey数组
        newObj[newkey[i]] = obj[newkey[i]]; //向新创建的对象中按照排好的顺序依次增加键值对

        str += (newkey[i] + obj[newkey[i]])
    }



    return {
        newObj,
        preSign: str
    }; //返回排好序的新对象
}

let data = {
  test: 1
}

let dataAdd = {
        ...data,
        timestamp: Math.round(new Date() / 1000), // 时间戳时间转换为秒
        // nonce: Math.floor(Math.random() * 100000000), // 8位随机数
        nonce: guid(20), // 20位随机数
    }

let secretKey = 'abc123';
let newData = objKeySort(dataAdd); 
    
let str = `${secretKey}${newData.preSign}`
let signStr = md5Libs.md5(str);
dataAdd.sign = signStr;

// nonce: "uoTgvAKQzJD0wFy3o18a"
// sign: "3b67af41142d1b8d5a2782a8479c8858"
// test: "1"
// timestamp: 1628323310

 

服务器端

  

<?php

namespace app\api\middleware;


use app\Request;
use app\services\user\UserAuthServices;
use crmeb\enum\EnumStoreRedisKey;
use crmeb\exceptions\AuthException;
use crmeb\interfaces\MiddlewareInterface;
use mySign\Sign;
use think\exception\DbException;
use think\facade\Cache;


/**
 * Class SignVerifyApi
 * @package app\api\middleware
 * 验证私钥签名的接口 (针对后端)
 */
class SignVerifyApi implements MiddlewareInterface
{
    public function handle(Request $request, \Closure $next, bool $force = true)
    {
        $secretKey = 'abc123'; // 秘钥
        $where     = $request->param();


        // 验证请求接口是否超时 开始
        $time        = time();  //获取当前时间戳
        $expire_date = 60; // 秒 / 请求和当前时间相差的时间. 如: 1分钟之前的时间戳不能请求. 请求超时1分钟的保错. 请求时间戳和服务器时间相差60秒报错

        $redis_nonce_expire = 70; // 随机字符串保存过期时间

        $diff_time = $time - ($where['timestamp']);

        if ($diff_time > $expire_date) {
            return app('json')->fail('请求超时,请检查请求时间和服务器时间是否相差过大');
        }
        // 验证请求接口是否超时 结束


        // 防重放
        // 保存随机数到redis, 并验证redis随机数 开始

        $redis_nonce = Cache::store('redis')->sIsMember(EnumStoreRedisKey::ADDRESS_CREATE_NONCE() . $secretKey, $where['nonce']);
        if ($redis_nonce) {
            return app('json')->fail('请勿使用请求过的参数');
        }


        Cache::store('redis')->sAdd(EnumStoreRedisKey::ADDRESS_CREATE_NONCE() . $secretKey, $where['nonce']);

        // 查找随机数列表元素数量
        $count = Cache::store('redis')->sCARD(EnumStoreRedisKey::ADDRESS_CREATE_NONCE() . $secretKey);

        // redis 没有随机数的时候则设置过期时间, 否则每次都会重置
        if ($count == 1) {
            Cache::store('redis')->expire(EnumStoreRedisKey::ADDRESS_CREATE_NONCE() . $secretKey, $redis_nonce_expire);//设置过期时间为秒
        }

        // 保存随机数到redis, 并验证redis随机数 结束

        // 验证签名开始
        $sign_result = Sign::generateSign($where, $secretKey);
        if ($sign_result['result']['sign'] !== $where['sign']) {
            return app('json')->fail('签名验证失败');
        }
        // 验证签名结束


        return $next($request);
    }
}

 

 

 

 

标签:nonce,newkey,请求,api,redis,secretKey,API,签名
来源: https://www.cnblogs.com/LBCBlog/p/15112374.html