如何保证API接口安全
接口的安全性主要围绕Token、Timestamp和Sign三个机制展开设计,保证接口的数据不会被篡改和重复调用,下面具体来看:
Token授权机制:用户使用用户名密码登录后服务器给客户端返回一个Token(通常是UUID),并将Token-UserId以键值对的形式存放在缓存服务器中。服务端接收到请求后进行Token验证,如果Token不存在,说明请求无效。
时间戳超时机制:用户每次请求都带上当前时间的时间戳timestamp,服务端接收到timestamp后跟当前时间进行比对,如果时间差大于一定时间(比如5分钟),则认为该请求失效,这个时间要保证足够完成本次请求的同时尽量短,可以减少缓存服务器的压力(见签名机制)。
签名机制:将Token和时间戳加上其他请求参数就行MD5或SHA-1算法(可根据情况加点盐)加密,加密后的数据为本次请求的签名sign,并将该签名存放到缓存服务器中,超时时间设定为跟时间戳的超时时间一致(这就是为什么要尽量短,二者时间一致可以保证无论在timestamp规定时间内还是外本URL都只能访问一次)。服务端接收到请求后以同样的算法得到签名,并跟当前的签名进行比对,如果不一样,说明参数被更改过,直接返回错误标识。同一个签名只能使用一次,如果发现缓存服务器中已经存在了本次签名,则拒绝服务。
sign生成规则及步骤:
① 第一步:将所有需要发送至服务端的请求参数(空参数值的参数、文件、字节流、sign除外)按照参数名ASCII码从小到大排序(字典序)
注意:
l 参数名ASCII码从小到大排序(字典序);
l 如果参数的值为空不参与签名;
l 文件、字节流不参与签名;
l sign不参与签名;
l 参数名、参数值区分大小写;
②第二步:将排序后的参数按照URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串strA;
③第三步:在strA后面拼接上apiKey得到striSignTemp字符串,将strSignTemp字符串转换为小写字符串后进行MD5运算,MD5运算后得到值作为sign的值传入服务端;
/*
* @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;
}
签名校验:
/*
* @desc 签名校验
* @param $token string 服务端分配的标识(不同客户端需使用不同的标识)
* @param $timestamp string 时间戳,UTC时间,以北京时间东八区(+8)为准
* @param $sign string 签名
* @param $privatekey string 私钥
* @param $data 业务参数json格式
* @return bool
*/
private function checkAuth($token,$timestamp,$sign,$privatekey,$data){
//参数判断
if(empty($token)){
show(10001,'token不能为空!');
}
if(empty($timestamp)){
show(10002,'时间戳不能为空!');
}
if(empty($data)){
show(10004,'业务参数不能为空!');
}
if(empty($sign)){
show(10006,'签名不能为空!');
}
if(empty($privatekey)){
show(10007,'私钥不能为空!');
}
//时间校验
$expire_second=config('expire_second');
$timestamp_t=$timestamp+$expire_second;
if($timestamp_t
拒绝重复调用:客户端第一次访问时,将签名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 = config('api_ask_limit');
//有效时间内,单位:秒
$limit_time = config('api_ask_time');
$now_time = time();
$valid_time = $now_time - $limit_time;
$ipwhere['ip_name'] = $client_ip;
$ipwhere['ask_url'] = $ask_url;
$ipwhere['creatime'] = array('>=',date('Y-m-d H:i:s',$valid_time));
$check_result = Db::table('log_ip_ask')->where($ipwhere)->count();
if($check_result !=='0'){
if($check_result >= $limit_num){
show(10010,'已经超出了限制次数!');
}
}
//执行插入
$add_data = array(
'ip_name'=>$client_ip,
'ask_url'=>$ask_url,
'creatime'=>date('Y-m-d H:i:s',time())
);
$result = Db::table('log_ip_ask')->insert($add_data);
if($result===false){
show(10011,'限制次数写入记录失败!');
}
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(
'192.168.0.111',
'192.168.0.112',
'192.168.0.113',
);
/**
* @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;
}
传输安全HTTPS
互联网发展到今天,大家越来越重视自己的隐私,各大公司也越来越重视数据的安全。传输过程中的数据安全解决方案主要是“HTTPS”,能够有效防止中间人攻击等。