php接口安全设计浅谈

简介: php接口安全设计浅谈

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

===================================================================

(1)Token授权机制:(**Token是客户端访问服务端的凭证)--**用户使用用户名密码登录后服务器给客户端返回一个Token(

通常是UUID

),并将Token-UserId以键值对的形式存放在缓存服务器中。服务端接收到请求后进行Token验证,如果Token不存在,说明请求无效。

(2)时间戳超时机制:(签名机制保证了数据不会被篡改)用户每次请求都带上当前时间的时间戳timestamp,服务端接收到timestamp后跟当前时间进行比对,如果时间差大于一定时间(

比如5分钟

),则认为该请求失效。时间戳超时机制是防御DOS攻击的有效手段。

(3)签名机制:将 Token时间戳 加上其他请求参数再用MD5或SHA-1算法(可根据情况加点盐)加密,加密后的数据就是本次请求的签名sign,服务端接收到请求后以同样的算法得到签名,并跟当前的签名进行比对,如果不一样,说明参数被更改过,直接返回错误标识。

01ebd755782e4c909dad0843d3544acf.jpeg

/**
 * @desc 接受参数处理
 */
private function dealParam(){
    //接受header参数--系统参数
    $systemParam=getAllHeadersParam();
    //接受body数据--业务参数(json格式)
    $data=file_get_contents('php://input');
    //读取配置文件中的私钥信息
    $api_apiKey=C('api_apiKey');
    $privatekey=$api_apiKey[$systemParam['token']];
    $arr['token']    =$systemParam['token'];        //服务端分配的标识(不同客户端需使用不同的标识)
    $arr['timestamp']=$systemParam['timestamp'];    //时间戳,UTC时间,以北京时间东八区(+8)为准
    $arr['version']  =$systemParam['version'];      //版本号
    $arr['sign']     =$systemParam['sign'];         //签名
    $arr['source']   =$systemParam['source'];       //来源(0-安卓/1-IOS/2-H5/3-PC/4-php/5-java)
    $arr['data'] =json_decode($data,true); //业务参数json格式 
    $arr['method'] =$data['method']; //访问接口,格式:模型名.方法名 
    return $arr;
 }

/*
 * @desc 获取所有以HTTP开头的header参数
 * @return array
 */
private function getAllHeadersParam(){
   $headers = array();
   foreach($_SERVER as $key=>$value){
       if(substr($key, 0, 5)==='HTTP_'){
           $key = substr($key, 5);
           $key = str_replace('_', ' ', $key);
           $key = str_replace(' ', '-', $key);
           $key = strtolower($key);
           $headers[$key] = $value;
       }
   }
   return $headers;
}

/*
 * @desc 签名校验
 * @param $token string 服务端分配的标识(不同客户端需使用不同的标识)
 * @param $timestamp string 时间戳,UTC时间,以北京时间东八区(+8)为准
 * @param $version string 版本号
 * @param $sign string 签名
 * @param $source int 来源(0-安卓/1-IOS/2-H5/3-PC/4-php/5-java)
 * @param $privatekey string 私钥
 * @param $data 业务参数json格式
 * @return bool
 */
private function checkAuth($token,$timestamp,$version,$sign,$source,$privatekey,$data){
        //参数判断
        if(empty($token)){
            E('token不能为空!');
        }
        if(empty($timestamp)){
            E('时间戳不能为空!');
        }
        if(empty($version)){
            E('版本号不能为空!');
        }
        if(empty($data)){
            E('业务参数不能为空!');
        }
        if(empty($source) && $source<>'0'){
            E('来源不能为空!');
        }
        if(empty($sign)){
            E('签名不能为空!');
        }
        if(empty($privatekey)){
            E('私钥不能为空!');
        }
        //时间校验
        $expire_second=C('expire_second',null,10);
        $timestamp_t=$timestamp+$expire_second;
        if($timestamp_t<time()){
            E('请求已经过期!');
        }
        $public= D('public');
        $datas=$this->original;
        //系统参数
        $paramArr=array(
            'token'=>$token,
            'timestamp'=>$timestamp,
            'version'=>$version,
            'source'=>$source,
            'data'=>$data,
        );
        //按规则拼接为字符串
        $str = $this->createSign($paramArr,$this->privatekey);
        if($str != $this->sign){
            E('验签错误!');
        }
        return true;
    }

sign生成规则及步骤:

① 第一步:将所有需要发送至服务端的请求参数(空参数值的参数、文件、字节流、sign除外)按照参数名ASCII码从小到大排序(字典序)

注意:

l 参数名ASCII码从小到大排序(字典序);

l 如果参数的值为空不参与签名;

l 文件、字节流不参与签名;

l sign不参与签名;

l 参数名、参数值区分大小写;

② 第二步:将排序后的参数按照URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串strA;

③ 第三步:在strA后面拼接上apiKey得到striSignTemp字符串,将strSignTemp字符串转换为小写字符串后进行MD5运算,MD5运算后得到值作为sign的值传入服务端;

示例(所有参数、参数值均为示例,开发人员参考格式即可):

token:cd171009328172Ad3sc

apiKey:cd13H2ddd22212ds1da

① 第一步(获取到的请求参数并按照参数名ASCII码从小到大排序):

token=cd173309328172Ad322

data={"userName":"18817201899",goods:["addrId":323,{"skuNo":"p12232-023","count":3},{"skuNo":"p12232-013","count":1}]}

timestamp=1507537036

version=v3.6.0

② 第二步(按规则拼接为字符串strA):

token=cd171009328172Ad3sc&data={"userName":"18817201899",goods:["addrId":323,{"skuNo":"p12232-023","count":3},{"skuNo":"p12232-013","count":1}]}timestamp=1507537036&version=v3.6.0

③ 第三步(生成sign):

1)待签名字符串strSignTemp:

token=cd171009328172Ad3sc&data={"userName":"18817201899",goods:["addrId":323,{"skuNo":"p12232-023","count":3},{"skuNo":"p12232-013","count":1}]}timestamp=1507537036&version=v3.6.0cd13H2ddd22212ds1da

2)转换为小写字符串

strtolower()

3)MD5加密后的密文

6D556D52822658FD47F7FE362544CEE1

/*
 * @desc 签名函数
 * @param $paramArr 系统参数
 * @param $apiKey 私钥
 * @return string 返回签名
 */
private function createSign ($paramArr,$apiKey) {
    ksort($paramArr);
    $sign='';
    foreach ($paramArr as $key => $val) {
        if ($key != '' && $val != '') {
            $sign .= $key."=".$val."&";
        }
    }
    $sign=rtrim($sign,"&");
    $sign.=$apiKey;
    $sign=strtolower($sign);
    $sign = md5($sign);
    return $sign;
}

(4)拒绝重复调用:客户端第一次访问时,将签名sign存放到缓存服务器中,超时时间设定为跟时间戳的超时时间一致,二者时间一致可以保证无论在timestamp限定时间内还是外 URL都只能访问一次。如果有人使用同一个URL再次访问,如果发现缓存服务器中已经存在了本次签名,则拒绝服务。如果在缓存中的签名失效的情况下,有人使用同一个URL再次访问,则会被时间戳超时机制拦截。这就是为什么要求时间戳的超时时间要设定为跟时间戳的超时时间一致。拒绝重复调用机制确保URL被别人截获了也无法使用(如抓取数据)。

/**
 * @desc 限制请求接口次数
 * @return bool
 */
private function ask_count(){
    $client_ip = $this->sys_get_client_ip();
    $ask_url = $this->sys_GetCurUrl();
    //限制次数
    $limit_num = C('api_ask_limit',null,5); 
    //有效时间内,单位:秒
    $limit_time = C('api_ask_time'); 
    $now_time = time();
    $valid_time = $now_time - $limit_time;
    $ipwhere['creatime'] = array('EGT',date('Y-m-d H:i:s',$valid_time));
    $ipwhere['ip_name'] = $client_ip;
    $ipwhere['ask_url'] = $ask_url;
    $check_result = M('log_ip_ask')->where($ipwhere)->count();  
    if($check_result !=='0'){  
        if($check_result >= $limit_num){  
            E('已经超出了限制次数!');
        }  
    }
    //执行插入
    $add_data = array(
        'ip_name'=>$client_ip,
        'ask_url'=>$ask_url,
        'creatime'=>date('Y-m-d H:i:s',time())
    );
    $result = M('log_ip_ask')->data($add_data)->add();
    if($result===false){
        E('写入记录失败!');
    }
    return true;
}

/**
 * 获取客户端IP地址
 * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字
 * @param boolean $adv 是否进行高级模式获取(有可能被伪装) 
 * @return mixed
 */
private function sys_get_client_ip($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
 */
private function sys_GetCurUrl() {
    $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;
}

非法ip限制访问,此处的限制一般用在服务器间的接口调用做此限制

// 允许访问的IP列表    
    private $ip_allow = array(
        '111.11.111.111', // 局域网ip
        '111.11.111.112', // 任务服务器
        '111.11.111.113', // 代理IP
    );
   /**
     * @desc 非法IP限制访问
     * @param array $config
     * @return bool
     */
   private function illegalip(){
        if(!$this->ip_limit){
            return true;
        }
        $remote_ip = get_client_ip();
        if(in_array($remote_ip, $ip_allow)){
            return true;
        }
        return false;
    }

目录
相关文章
|
4月前
|
SQL 存储 安全
PHP 与现代 Web 应用的安全挑战与解决方案
随着 Web 应用的发展,PHP 作为一种广泛使用的服务器端脚本语言,面临着越来越复杂的安全挑战。本文探讨了当前 PHP 开发中常见的安全问题,并提供了相应的解决方案,帮助开发者构建更安全可靠的 Web 应用。 【7月更文挑战第8天】
71 1
|
22天前
|
SQL 安全 Go
PHP在Web开发中的安全实践与防范措施###
【10月更文挑战第22天】 本文深入探讨了PHP在Web开发中面临的主要安全挑战,包括SQL注入、XSS攻击、CSRF攻击及文件包含漏洞等,并详细阐述了针对这些风险的有效防范策略。通过具体案例分析,揭示了安全编码的重要性,以及如何结合PHP特性与最佳实践来加固Web应用的安全性。全文旨在为开发者提供实用的安全指南,帮助构建更加安全可靠的PHP Web应用。 ###
32 1
|
2月前
|
Java PHP 数据安全/隐私保护
PHP 面向对象,构造函数,析构函数,继承,方法的重写,接口抽象类,static,final,this,parent,self的异同和作用
本文详细介绍了PHP面向对象编程的一系列核心概念和用法,包括构造函数、析构函数、继承、方法重写、访问控制、接口、抽象类、静态成员、final关键字、以及this、self、parent这三个关键字的异同和作用。通过具体示例代码,展示了如何在PHP中使用这些面向对象的特性,以及它们在实际开发中的应用。
PHP 面向对象,构造函数,析构函数,继承,方法的重写,接口抽象类,static,final,this,parent,self的异同和作用
|
3月前
|
网络协议 API PHP
PhalApi:在宝塔一键安装部署PHP开源接口框架的教程
要在宝塔面板上一键安装部署PhalApi开源接口框架,首先进入宝塔软件商店,切换到“一键部署”选项,搜索“phalapi”并点击“一键部署”。安装时需填写接口域名、数据库名及密码,提交后等待安装完成。安装成功后可在宝塔面板中查看新站点和源代码目录,并通过DNS解析设置访问接口域名,如`http://myapi.phalapi.net/`。默认开启的调试模式便于测试,可通过修改`config/sys.php`中的`debug`值为`false`关闭。最后,在源代码中开发自己的PHP接口,PhalApi会自动生成在线接口文档,方便后续调用与维护。更多详细教程可参考官方文档。
|
3月前
|
Ubuntu 应用服务中间件 Linux
如何在Ubuntu 14.04上使用Nginx和Php-fpm安全地托管多个网站
如何在Ubuntu 14.04上使用Nginx和Php-fpm安全地托管多个网站
30 0
|
4月前
|
Java API PHP
【亲测有效,官方提供】php版本企查查api接口请求示例代码,php请求企查查api接口,thinkphp请求企查查api接口
【亲测有效,官方提供】php版本企查查api接口请求示例代码,php请求企查查api接口,thinkphp请求企查查api接口
139 1
|
5月前
|
JSON 安全 API
实战指南:使用PHP构建高性能API接口服务端
构建RESTful API的简要指南:使用PHP和Laravel,先安装Laravel并配置数据库,接着在`api.php`中定义资源路由,创建`PostController`处理CRUD操作,定义`Post`模型与数据库交互。使用Postman测试API功能,如创建文章。别忘了关注安全性、错误处理和性能优化。
140 2
|
5月前
|
自然语言处理 安全 PHP
PHP 之道笔记整理:最佳实践与安全指南
这篇文章讨论了PHP开发中的最佳实践,包括使用最新稳定版(PHP 8.3)以提升性能和安全,利用`DateTime`类及Carbon库处理日期时间,确保使用UTF-8编码并用`mb_*`函数处理字符串,以及通过密码哈希和数据过滤来加强Web应用安全。文章提醒开发者始终保持对新技术和安全实践的关注。
93 2
|
5月前
|
存储 安全 PHP
安全开发-PHP应用&文件管理模块&显示上传&黑白名单类型过滤&访问控制&文件管理模块&包含&上传&遍历&写入&删除&下载&安全
安全开发-PHP应用&文件管理模块&显示上传&黑白名单类型过滤&访问控制&文件管理模块&包含&上传&遍历&写入&删除&下载&安全
|
6月前
|
安全 前端开发 PHP
采用PHP开发的医院安全(不良)事件系统源码 医院不良事件有哪些?又该怎样分类呢?也许这篇文章能给予你答案。
医疗安全不容忽视! 医疗不良事件有哪些?又该怎样分类呢?也许这篇文章能给予你答案。
60 1
采用PHP开发的医院安全(不良)事件系统源码 医院不良事件有哪些?又该怎样分类呢?也许这篇文章能给予你答案。