Sign(签名) 是接口验证安全性的一种常用的技术,在一定程度上,可以保证我们数据接口得安全性, Sign一般都需要配合加密算法来使用,常用的AES系列算法(对称算法)、SHA系列算法(安全散列算法)、RSA系列算法(非对称算法)等,我这边现在使用的AES对称加密算法。
AES加密和解密示例代码
<?php namespace app\common\lib; class Aes{ public $key = ''; public $iv = ''; /** * 构造方法 */ public function __construct($config){ foreach($config as $k=>$v){ $this->$k = $v; } } // 加密 public function aesEn($data){ return base64_encode(openssl_encrypt($data, $this->method,$this->key, OPENSSL_RAW_DATA , $this->iv)); } //解密 public function aesDe($data){ return openssl_decrypt(base64_decode($data), $this->method, $this->key, OPENSSL_RAW_DATA, $this->iv); } } ?>
使用一个专门的类来实现生成Sign和验证Sign
<?php namespace app\common\lib; use think\facade\Cache; class IAuth{ /** * md5 加密 */ public static function serPassword($data){ return md5($data,\config('api.password_pre_halt')); } /** * 生成每次请求Sign字符串 */ public static function setSign($data = []) { // 按字段排序 \ksort($data); //拼接字符串数据 $string = \http_build_query($data); //通过 aes 来加密 $string = (new Aes(\config('api.aes_key')))->aesEn($string); return $string; } /** * 检测sign是否正常 */ public static function checkSignPass($data) { $str = (new Aes(\config('api.aes_key')))->aesDe($data); // 判断解析出来的数据是否为空 if(empty($str)){ return false; } // 字符串转数组 parse_str($str,$arr); // 判断是否是数组,数组内的字段是否正确 if(!\is_array($arr) || empty($arr['mg'])){ return false; } // 检测缓存,如果有缓存,说明这个sign已经被使用 if(Cache::get($data)){ return false; } return true; } } ?> 备注: 'aes_key' =>[ 'key' => 'reter4446fdfgdfgdfg', //加密key,这个可以随便定义 'iv' => md5(time(). uniqid(),true), //保证偏移量为16位 'method' => 'AES-128-CBC' //加密方式 # AES-256-CBC等,这个可以搭配的形式有很多,具体的你可以去了解一下AES加密算法 ],
Sign验证的基础类:Common
sign验证基本上在每个网络请求中都需要做的,所有我们把Sign验证封装到基础类中,这个做会更合理。
<?php namespace app\api\controller; use think\Controller; use app\common\lib\exception\ApiException; use think\facade\Config; use app\common\lib\IAuth; use think\facade\Cache; class Common extends Controller{ public $headers =''; /** * 初始化方法 */ public function initialize(){ $this->checkRequestAuth(); } /** * 检测app请求的Sign数据是否合法 */ public function checkRequestAuth(){ //1. 获取请求头 $headers = $this->request->header(); //2. 判断sign是否为空 if(empty($headers['sign'])){ throw new ApiException('sign不能为空',400); } //3.检验设备 if(!in_array($headers['app_types'],\config('api.apptypes'))){ throw new ApiException('设备型号不符合',400); } // 测试代码: 手动生成sign,实际开发中,这段代码是由客户端来执行. $aesData = IAuth::setSign(\input('post.')); //4.检验sign if(!IAuth::checkSignPass($aesData)){ throw new ApiException('sign验证不通过!',401); } //5.设置缓存(未用到,只是用来学习缓存技术) Cache::set($headers['sign'],1,\config('api.app_sign_cache_time')); // 获取缓存 echo Cache::get($headers['sign']); // 保存header中的值 $this->headers = $headers; } } ?>
在以前,我们服务端还会使用比对时间戳的方式来验证Sign是否过期,还是用本地缓存(Cache)技术来验证该Sign字符串是否已经被请求过,保证Signj机制更加安全。