因为前后端分离,所以接口必须暴露在公网环境,某些恶意用户会通过浏览器控制台查看接口,然后使用HTTP请求工具调用接口,因为请求工具可以伪造请求头等信息,通过请求头判断是否为恶意请求难度比较大,所以要对参数进行签名。
前端参数签名操作方法
// 1. 传给后端的参数对象,增加时间戳参数let data = {id: 1, name: 11, channel_id:1, timestamp: 123456789};// 2. 获取data的key,排序规则按照第一个字符的ASCII码值递增排序let keys = Object.keys(data).sort()// 3. 将参数按照一定的规则拼接成字符串let buff = '';keys.forEach((key) => {buff += key + '=' + data[key] + '&'})buff = buff.substr(0, buff.length - 1)// 4. 加密拼接好的字符串signature = encrypt(buff);// 5. 将密文传给后端data.signature = signature;/*** 加密方法* @param buff*/function encrypt(buff) {// 进入签名加密操作return buff;}
后端签名校验方法
/*** 前端参数加密,防开挂校验,防止篡改参数,防止重复调用* @param array $params 前端传的所有参数* @throws BusinessException*/public static function checkSignature(array $params) {if (!self::isNotEmpty($params, BasicConstant::SIGNATURE))throw new BusinessException('参数错误,缺少签名');Log::info('前端参数,排序前: ' . json_encode($params));// 1. 参数排序ksort($params);Log::info('排序后: ' . json_encode($params));// 2. 将参数拼接成字符串,除了 signature 签名之外都用 key=value& 的方式拼接$buff = '';foreach ($params as $k => $v) {if ($k != BasicConstant::SIGNATURE) {$buff .= $k . "=" . $v . "&";}}// 去除最后一个多余的 &$buff = trim($buff, "&");Log::info('拼接后: ' . $buff);// 3. 将签名放入 redis,如果 redis 中存在此签名,说明重复请求$encrypt = self::desEncrypt($buff);Log::info('加密后的密文: ' . $encrypt);$exists = RedisService::setExNx($encrypt, true, 10);if (!$exists)throw new BusinessException("非法请求,重复调用");// 4. 解密校验参数$decrypt = self::desDecrypt($params[BasicConstant::SIGNATURE]);Log::info('解密:' . $decrypt);// 如果签名解密后与接收到的实际参数不一致,说明参数被篡改if ($buff != $decrypt)throw new BusinessException('非法请求,参数可能被篡改');$decryptParams = self::convertUrlQuery($decrypt);Log::info('解密后解析成数组:' . json_encode($decryptParams));// 判断是否超时if (time() - $decryptParams['timestamp'] > 10)throw new BusinessException('签名超时');}/*** 自定义解密方法* @param $string* @return string*/public static function desEncrypt($string){return $string;}/*** 自定义加密方法* @param $string* @return string*/public static function desDecrypt($string){return $string;}
这样就能实现接口恶意调用的校验了,如果用户拿参数去重复调用的话会提示重复调用,如果用户修改了参数的话会提示参数被篡改。
