php接口安全设计及实现

1 篇文章 0 订阅

php接口安全设计实现

接口的安全性主要围绕Token、Timestamp和Sign三个机制展开设计,保证接口的数据不会被篡改和重复调用,下面具体来看:

(1)Token授权机制:(Token是客户端访问服务端的凭证)–用户使用用户名密码登录后服务器给客户端返回一个Token(通常是UUID),并将Token-UserId以键值对的形式存放在缓存服务器中。服务端接收到请求后进行Token验证,如果Token不存在,说明请求无效。
(2)时间戳超时机制:(签名机制保证了数据不会被篡改)用户每次请求都带上当前时间的时间戳timestamp,服务端接收到timestamp后跟当前时间进行比对,如果时间差大于一定时间(比如5分钟),则认为该请求失效。时间戳超时机制是防御DOS攻击的有效手段。
(3)签名机制:将 Token 和 时间戳 加上其他请求参数再用MD5或SHA-1算法(可根据情况加点盐)加密,加密后的数据就是本次请求的签名sign,服务端接收到请求后以同样的算法得到签名,并跟当前的签名进行比对,如果不一样,说明参数被更改过,直接返回错误标识。

简单的实现了接口校验:登陆获取到token

    /**
     * 模拟登陆 分配随机token
     * @route('login')
     */
    public function login()
    {
        $data = $this->request->param();

       if( $data['name']=="luozhengbo" &&   $data['pass'] == "123456"){
           $id=1;
           $token= Cache::get('token'.$id);
           if(!$token){
               //15c3463ffb65d9
               $token =$id.uniqid();
               Cache::set('token'.$id,$token,'3*60');
           }
           return json($token);
       }else{
           return json('用户或密码不正确');
       }


    }

用php模拟客服端,登陆成功获得token 并生成随机数、时间戳、生成sign ,生成这些数据之后请求接口时,带上相应的参数过去进行鉴权,鉴权验签部分用的工具就不贴代码了。

//随机生成字符串
    private function createNonceStr($length = 8) {
        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }
    
      /**
     * @param $timeStamp 时间戳
     * @param $randomStr 随机字符串
     * @return string 返回签名
     * @route('checklogin')
     */
    public  function createSign($timeStamp='',$token='',$randomStr=''){
        $arr['timeStamp'] = $timeStamp;
        $arr['randomStr'] = $randomStr;
        $arr['token'] = $token;
        //按照首字母大小写顺序排序
        sort($arr,SORT_STRING);
        //拼接成字符串
        $str = implode($arr);
        //进行加密
        $signature = sha1($str);
        $signature = md5($signature);
        //转换成大写
        $signature = strtolower($signature);
        return $signature;
    }
 /**
     * @return mixed
     * @route('userlogin')
     */
    public function login()
    {
        $token = json_decode(httpGet("10.20.1.172/index.php/login/name/luozhengbo/pass/123456"),true);
        return $token;
    }
    
    /**
     * @route('getradmon')
     */
    public function getDataFromServer(){
        //时间戳
        $timeStamp = time();
        //随机数
        $randomStr = $this -> createNonceStr();
        //生成签名
        $token = $this->login();
        $signature = $this -> createSign($timeStamp,$token,$randomStr);
        dump($token);
        dump($timeStamp);
        dump($randomStr);
        dump($signature);
        die;
    }

接下来就是服务端鉴权代码,服务端拿到timestamp、token、sign、randomStr等参数进行验证过程

namespace app\index\controller;

use think\facade\Cache;
use think\facade\Config;
use think\Controller;
use think\facade\exception;
use think\Db;

class common extends  controller
{

    //限制访问
    protected $ip_limit =array(
        '10.20.1.60',
        '10.20.1.62',
        '10.20.1.61',
    );

    /**
     * 签名检验
     * @route('auth')
     *
     */
    public function checkAuth($token,$randomStr,$timestamp,$sign)
    {
        //参数判断
        if(empty($token)){
            exception('token不能为空','500');
        }
        if(empty($timestamp)){
            exception('时间戳不能为空','500');
        }
        if(empty($randomStr)){
            exception('随机字符串不能为空','500');
        }
        if(empty($sign)){
            exception('签名不能为空','500');
        }
        //检验token
        $tokenCheck = Cache::get('1token');
        if($tokenCheck != $token ){
            exception('token已经过期','500');
        }
        //时间校验
        $exprie = Config::get('app.expire');
        $timestamp1 = $timestamp+$exprie;

        if( $timestamp1<time() ){//过期
            exception('请求已经过期','500');
        }
        if(!$this->illegalip()){
            return json('ip限制访问');
        }
        //按规则拼接为字符串
        $str = $this->arithmetic($timestamp,$token,$randomStr);
        if($str != $sign){
            exception('验签错误','500');
        }
        if(!$this->ask_count()){
            return json('验签error');
        }
        return json('验签ok');

    }

    /**
     * @param $timeStamp 时间戳
     * @param $randomStr 随机字符串
     * @return string 返回签名
     */
    public function arithmetic($timeStamp,$token,$randomStr){
        $arr['timeStamp'] = $timeStamp;
        $arr['randomStr'] = $randomStr;
        $arr['token'] = $token;
        //按照首字母大小写顺序排序
        sort($arr,SORT_STRING);
        //拼接成字符串
        $str = implode($arr);
        //进行加密
        $signature = sha1($str);
        $signature = md5($signature);
        //转换成小写
        $signature = strtolower($signature);
        return $signature;
    }

    /**
     * @desc 限制请求接口次数
     * @return bool
     */
    private function ask_count(){
        $client_ip = getClientIp();
        $ask_url = getUrl();
        //限制次数
        $limit_num = Config::get('api_ask_limit');
        //有效时间内,单位:秒
        $limit_time = Config::get('api_ask_time');
        $now_time = time();
        $valid_time = $now_time - $limit_time;
        $valid_time = date('Y-m-d H:i:s',$valid_time);
        $now_time = date('Y-m-d H:i:s',$now_time);
        $ipwhere['ip_name'] = $client_ip;
        $ipwhere['ask_url'] = $ask_url;
        $check_result = Db::name('log_ip_ask')
            ->whereBetweenTime('creatime',$valid_time,$now_time)
            ->where($ipwhere)
            ->count();
        if($check_result !=='0'){
            if($check_result >= $limit_num){
                exception('已经超出了限制次数');
            }
        }
        //执行插入
        $add_data = array(
            'ip_name'=>$client_ip,
            'ask_url'=>$ask_url,
            'creatime'=>date('Y-m-d H:i:s',time())
        );
        $result = Db::name('log_ip_ask')->insert($add_data);
        if($result===false){
            exception('已经超出了限制次数');
        }
        return true;
    }

    /**
     * @desc 非法IP限制访问
     * @param array $config
     * @return bool
     */
    private function illegalip(){
        if( !$this->ip_limit ){
            return true;
        }
        $remote_ip = getClientIp();
        if(in_array($remote_ip, $this->ip_limit)){
              return false;
        }
        return true;
    }

    /**
     * 模拟登陆 分配随机token
     * @route('login')
     */
    public function login()
    {
        $data = $this->request->param();

       if( $data['name']=="luozhengbo" &&   $data['pass'] == "123456"){
           $id=1;
           $token= Cache::get('token'.$id);
           if(!$token){
               //15c3463ffb65d9
               $token =$id.uniqid();
               Cache::set('token'.$id,$token,'3*60');
           }
           return json($token);
       }else{
           return json('用户或密码不正确');
       }

    }

}

最后还有几个公用的方法和配置

 
    'expire'=>5*6000000,//api有效期
    'api_ask_limit'=>10,//api访问次数
    'api_ask_time'=>60,

common中的函数

/**
 * 获取客户端IP地址
 * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字
 * @param boolean $adv 是否进行高级模式获取(有可能被伪装)
 * @return mixed
 */
function getClientIp($type = 0,$adv=false) {
    $type = $type ? 1 : 0;
    static $ip  =   NULL;
    if ($ip !== NULL) return $ip[$type];
    if($adv){
        if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $arr    =   explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
            $pos    =   array_search('unknown',$arr);
            if(false !== $pos) unset($arr[$pos]);
            $ip     =   trim($arr[0]);
        }elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
            $ip     =   $_SERVER['HTTP_CLIENT_IP'];
        }elseif (isset($_SERVER['REMOTE_ADDR'])) {
            $ip     =   $_SERVER['REMOTE_ADDR'];
        }
    }elseif (isset($_SERVER['REMOTE_ADDR'])) {
        $ip = $_SERVER['REMOTE_ADDR'];
    }
    // IP地址合法验证
    $long = sprintf("%u",ip2long($ip));
    $ip   = $long ? array($ip, $long) : array('0.0.0.0', 0);
    return $ip[$type];
}

/**
 * @desc php获取当前访问的完整url地址
 * @return string
 */
function getUrl() {
    $url = 'http://';
    if (isset ( $_SERVER ['HTTPS'] ) && $_SERVER ['HTTPS'] == 'on') {
        $url = 'https://';
    }
    if ($_SERVER ['SERVER_PORT'] != '80') {
        $url .= $_SERVER ['HTTP_HOST'] . ':' . $_SERVER ['SERVER_PORT'] . $_SERVER ['REQUEST_URI'];
    } else {
        $url .= $_SERVER ['HTTP_HOST'] . $_SERVER ['REQUEST_URI'];
    }
    return $url;
}
/**
 *
 * curl模拟get请求
 */
function httpGet($url){
    $curl = curl_init();
    //需要请求的是哪个地址
    curl_setopt($curl,CURLOPT_URL,$url);
    //表示把请求的数据已文件流的方式输出到变量中
    curl_setopt($curl,CURLOPT_RETURNTRANSFER,1);
    $result = curl_exec($curl);
    curl_close($curl);
    return $result;
}

以上还有可改进的地方,限制ip、次数等。大家有什么好的建议可以说一下。
参考:https://www.cnblogs.com/zouke1220/p/9394356.html

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值