假设我们在做TP5项目的时候 需要编写api接口提供给外部进行请求,过程中肯定会涉及到安全问题:
主要是:
利用以上的问题 想攻击你的人 可以不断去请求你的后台 或者往你的后台塞东西进去 又或者是通过抓包的形式 拿到你的数据值之后不断的攻击 因此 考虑到时效性 唯一性 需要实现一个sign字段 在规定的时间内失效 没有这个字段无法请求 并且是唯一的
以下代码实现:
加密方式:MD5 AES RSA 我们采用AES加密,基本参数都放入header(sign version app_type did model)
每次httpq请求都携带sign(放入header)
首先引入一个Aes文件到目录:application\common\lib\Aes.php(文件夹不存在就新建)
简单介绍Aes文件的代码:
构造函数中的key 需要进行配置
encrypt() 加密的方法
decrypt() 解密的方法
key = config('app.aeskey');
}
/**
* 加密 加密模式也要一致 pkcs5
* @param String input 加密的字符串
* @param String key 解密的key
* @return HexString
*/
public function encrypt($input = '') {
$size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
$input = $this->pkcs5_pad($input, $size);
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
mcrypt_generic_init($td, $this->key, $iv);
$data = mcrypt_generic($td, $input);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
$data = base64_encode($data);
return $data;
}
/**
* 填充方式 pkcs5
* @param String text 原始字符串
* @param String blocksize 加密长度
* @return String
*/
private function pkcs5_pad($text, $blocksize) {
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}
/**
* 解密
* @param String input 解密的字符串
* @param String key 解密的key
* @return String
*/
public function decrypt($sStr) {
$decrypted= mcrypt_decrypt(MCRYPT_RIJNDAEL_128,$this->key,base64_decode($sStr), MCRYPT_MODE_ECB);
$dec_s = strlen($decrypted);
$padding = ord($decrypted[$dec_s-1]);
$decrypted = substr($decrypted, 0, -$padding);
return $decrypted;
}
}
key的配置以及一些sign的配置 代码贴上 :
位置:application\extra\app.php
'zzs',//密码加密
'aeskey'=>'zzs45747ss223455',//aes加密的密钥 客户端和服务端一致
//app_type配置数组
'app_types'=>[
'ios','android'
],
'app_sign_time'=>10,//sign失效时间 秒 测试的时候 设置长一些
'app_sign_cache_time'=>20,//sign缓存的有效时间
];
现在编写common基类 所有的api接口去继承它! 代码的讲解都写在注释中
checkRequestAuth();
$this->testAes();
}
//验证数据是否合法 检查每次请求
public function checkRequestAuth(){
//首先获取header
$headers = request()->header();
//进行参数校验
if(empty($headers['sign'])){
throw new ApiException('sign参数不存在!',400);
}
//判断app_type是否存在配置的数组中需要在app.php下进行配置application\extra\app.php
if(!in_array($headers['app_type'],config('app.app_types'))){
throw new ApiException('app_type不合法!',400);
}
//如果sign校验不能通过
if(!IAuth::checkSignPass($headers)){
throw new ApiException('授权码sign失败!',401);
}
//sign做唯一性处理 写入缓存
Cache::set($headers['sign'],1,config('app.app_sign_cache_time'));
//赋值给类的属性
$this->headers = $headers;
}
//测试aes
//sign AES加密 创建Aes类库 application\common\lib\Aes.php
//sign加密方法 封装在application\common\lib\IAuth.php
public function testAes(){
// $str = "id=1&ms=45&username=zzs";
// //调用aes类库下的加密方法
// $a = new Aes();
// //这是加密的方法
// $a->encrypt($str);
// //解密的方法
// $a->decrypt($res);
// exit;
//测试封装的sign加密方法
$data = [
'did'=>'18250305186',
'version'=>1,
'time'=>Time::get13TimeStamp(),//时间的类库
];
//加密结果 6c6gWTBo42bqv5XNyMYvy2ejqHAfeu+GWCHCJHAo7PA= 没有带时间的串
//6c6gWTBo42bqv5XNyMYvy+92AigFr1Pgmqa2XeKzQoVMQ1ADQBQaQ2mrP5zmM/mu 带时间的串
//echo IAuth::setSign($data);exit;
// //反转换
//$str = "6c6gWTBo42bqv5XNyMYvy2ejqHAfeu+GWCHCJHAo7PA=";//
//解密 did=12345dg&version=1
// echo (new Aes())->decrypt($str);exit;
}
}
以上代码中的 IAuth::checkSignPass 这边说明一下 就是创建一个类封装了sign加密方法和 检验sign是否能通过的方法,代码如下:位置:application\common\lib\IAuth.php
encrypt($string);
//返回结果
return $string;
}
//检验sign是否能通过
/**
* @param array $data $headers的数据
* @return boolean
*/
public static function checkSignPass($data){
//解密
$str = (new Aes())->decrypt($data['sign']);
//如果解密之后为空
if(empty($str)){
return false;
}
//将$str解析成多个变量
parse_str($str,$arr);
// array(2) {
// ["did"] => string(7) "12345dg"
// ["version"] => string(1) "1"
// }
//halt($arr);//打印出数据
//如果不是一个数组 或者 did的值为空 或者 did的值不等于提交过来的值
if(!is_array($arr) || empty($arr['did']) || $arr['did'] != $data['did']){
return false;
}
//判断是否在时间范围内 10分钟 超过时间sign的内容就错误了
if(time()-ceil($arr['time'] / 1000) > config('app.app_sign_time')){
return false;
}
//唯一性判定 如果存在 返回false
if(Cache::get($data['sign'])){
return false;
}
return true;
}
}
接下来开始测试,新建一个api接口Test.php(application\api\controller\Test.php)创建一个update方法 传参id
encrypt(input('put.')),201);
}
}
然后打开postman 选择请求方法是 put 然后api的地址 比如我的是localhost/test/1(localhost就是本地的ip)
选择headers标签 如下图:
然后点击send 接下来我们分析上面的那些代码 讲解以下实现过程:
1.这个test类是继承common类
所以走到这里 初始化方法里面 调用我们验证数据的方法
2.看下我们这个验证数据的方法checkRequestAuth()
首先先获取header的信息 这是一个数据
然后判断这个数组中有没有提交sign这个字段 如果没有 断开 提示报错
接下来再判断 提交的app类型是否在config配置中 如果不是 同样 断开 提示报错
然后调用IAuth类下面的方法 进行sign验证 看下代码:
通过这些判断 才能返回真 否则回到Common的文件中下的判断 抛出错误授权码sign失败!
以上的判断都能通过之后 写入缓存中 设置标识 才具有唯一性 并配置他的存储时间长度
然后赋值给header 这样在基类中进行判断无误之后 我们在Test.php下的update方法进行打印
在此之前 记得在postman的body标签 提交一些数据 比如我的:
在Test.php下的update方法下 我们进行打印:(此时app_sign_time的时间设置的长一些)
编写好了之后 回到postman 点击send
说明测试成功 data就是我们提交之后 调用方法 进行加密的结果
思路表达可能有些紊乱 欢迎在评论区留言 觉得有用 就点个赞 加关注吧