api 签名流程 API接口签名验证
作者:互联网
现在越来越多的公司以 API 的形式对外提供服务,这些 API 接口大多暴露在公网上,所以安全性就变的很重要了。最直接的风险如下:
- 非法使用 API 服务。(收费接口非法调用)
- 恶意攻击和破坏。(数据篡改、DOS)
因此需要设计一些接口安全保护的方式来增强接口安全,在运输层可添加 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