https://www.cnblogs.com/afsj/p/7424320.html
PHP做APP接口時,如何保證接口的安全性?
1、當用戶登錄APP時,使用https協議調用后台相關接口,服務器端根據用戶名和密碼時生成一個access_key,並將access_key保存在session中,將生成的access_key和session_id返回給APP端。
2、APP端將接收到的access_key和session_id保存起來
3、當APP端調用接口傳輸數據時,將所傳數據和access_key使用加密算法生成簽名signature,並將signature和session_id一起發送給服務器端。
4、服務器端接收到數據時,使用session_id從session中獲取對應的access_key,將access_key和接收到的數據使用同一加密算法生成對應signature,如果生成的簽名和接收到的signature相同時,則表明數據合法
https://www.cnblogs.com/zouke1220/p/9394356.html
php接口安全設計淺談
Token授權機制
時間戳超時機制:
簽名機制:
拒絕重復調用:客戶端第一次訪問時,將簽名sign存放到緩存服務器中,超時時間設定為跟時間戳的超時時間一致,二者時間一致可以保證無論在timestamp限定時間內還是外 URL都只能訪問一次。
非法ip限制訪問,此處的限制一般用在服務器間的接口調用做此限制 白名單
https訪問
接口的安全性主要圍繞Token、Timestamp和Sign三個機制展開設計,保證接口的數據不會被篡改和重復調用,下面具體來看:
(1)Token授權機制:(Token是客戶端訪問服務端的憑證)--用戶使用用戶名密碼登錄后服務器給客戶端返回一個Token(通常是UUID),並將Token-UserId以鍵值對的形式存放在緩存服務器中。服務端接收到請求后進行Token驗證,如果Token不存在,說明請求無效。
(2)時間戳超時機制:(簽名機制保證了數據不會被篡改)用戶每次請求都帶上當前時間的時間戳timestamp,服務端接收到timestamp后跟當前時間進行比對,如果時間差大於一定時間(比如5分鍾),則認為該請求失效。時間戳超時機制是防御DOS攻擊的有效手段。
(3)簽名機制:將 Token 和 時間戳 加上其他請求參數再用MD5或SHA-1算法(可根據情況加點鹽)加密,加密后的數據就是本次請求的簽名sign,服務端接收到請求后以同樣的算法得到簽名,並跟當前的簽名進行比對,如果不一樣,說明參數被更改過,直接返回錯誤標識。
復制代碼
/**
-
@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;
}
復制代碼
參考鏈接:https://www.jianshu.com/p/c6518a8f4040
App開放接口api安全性—Token簽名sign的設計與實現
前言
在app開放接口api的設計中,避免不了的就是安全性問題,因為大多數接口涉及到用戶的個人信息以及一些敏感的數據,所以對這些 接口需要進行身份的認證,那么這就需要用戶提供一些信息,比如用戶名密碼等,但是為了安全起見讓用戶暴露的明文密碼次數越少越好,我們一般在web項目 中,大多數采用保存的session中,然后在存一份到cookie中,來保持用戶的回話有效性。但是在app提供的開放接口中,后端服務器在用戶登錄后 如何去驗證和維護用戶的登陸有效性呢,以下是參考項目中設計的解決方案,其原理和大多數開放接口安全驗證一樣,如淘寶的開放接口token驗證,微信開發 平台token驗證都是同理。
簽名設計
對於敏感的api接口,需使用https協議
https是在http超文本傳輸協議加入SSL層,它在網絡間通信是加密的,所以需要加密證書。
https協議需要ca證書,一般需要交費。
簽名的設計
原理:用戶登錄后向服務器提供用戶認證信息(如賬戶和密碼),服務器認證完后給客戶端返回一個Token令牌,用戶再次獲取信息時,帶上此令牌,如果令牌正取,則返回數據。對於獲取Token信息后,訪問用戶相關接口,客戶端請求的url需要帶上如下參數:
時間戳:timestamp
Token令牌:token
然后將所有用戶請求的參數按照字母排序(包括timestamp,token),然后更具MD5加密(可以加點鹽),全部大寫,生成sign簽名,這就是 所說的url簽名算法。然后登陸后每次調用用戶信息時,帶上sign,timestamp,token參數。
例如:原請求https://www.andy.cn/api/user/update/info.shtml?city=北京 (post和get都一樣,對所有參數排序加密)
加上時間戳和token
https://www.andy.cn/api/user/update/info.shtml?city=北京×tamp=12445323134&token=wefkfjdskfjewfjkjfdfnc
然后更具url參數生成sign
最終的請求如
https://www.andy.cn /api/user/update/info.shtml?city=北京×tamp=12445323134& token=wefkfjdskfjewfjkjfdfnc&sign=FDK2434JKJFD334FDF2
其最終的原理是減小明文的暴露次數;保證數據安全的訪問。
具體實現如下:
- api請求客戶端想服務器端一次發送用用戶認證信息(用戶名和密碼),服務器端請求到改請求后,驗證用戶信息是否正確。
如果正確:則返回一個唯一不重復的字符串(一般為UUID),然后在Redis(任意緩存服務器)中維護Token----Uid的用戶信息關系,以便其他api對token的校驗。
如果錯誤:則返回錯誤碼。
2.服務器設計一個url請求攔截規則
(1)判斷是否包含timestamp,token,sign參數,如果不含有返回錯誤碼。
(2)判斷服務器接到請求的時間和參數中的時間戳是否相差很長一段時間(時間自定義如半個小時),如果超過則說明該 url已經過期(如果url被盜,他改變了時間戳,但是會導致sign簽名不相等)。
(3)判斷token是否有效,根據請求過來的token,查詢redis緩存中的uid,如果獲取不到這說明該token已過期。
(4)根據用戶請求的url參數,服務器端按照同樣的規則生成sign簽名,對比簽名看是否相等,相等則放行。(自然url簽名 也無法100%保證其安全,也可以通過公鑰AES對數據和url加密,但這樣如果無法確保公鑰丟失,所以簽名只是很大程 度上保證安全)。
(5)此url攔截只需對獲取身份認證的url放行(如登陸url),剩余所有的url都需攔截。
3.Token和Uid關系維護
對於用戶登錄我們需要創建token--uid的關系,用戶退出時需要需刪除token--uid的關系。
簽名實現
獲取全部請求參數
復制代碼
String sign = request.getParameter("sign");
Enumeration<?> pNames = request.getParameterNames();
Map<String, Object> params = new HashMap<String, Object>();
while (pNames.hasMoreElements()) {
String pName = (String) pNames.nextElement();
if("sign".equals(pName))continue;
Object pValue = request.getParameter(pName);
params.put(pName, pValue);
}
復制代碼
生成簽名
復制代碼
public static String createSign(Map<String, String> params, boolean encode)
throws UnsupportedEncodingException {
Set
Object[] keys = keysSet.toArray();
Arrays.sort(keys);
StringBuffer temp = new StringBuffer();
boolean first = true;
for (Object key : keys) {
if (first) {
first = false;
} else {
temp.append("&");
}
temp.append(key).append("=");
Object value = params.get(key);
String valueString = "";
if (null != value) {
valueString = String.valueOf(value);
}
if (encode) {
temp.append(URLEncoder.encode(valueString, "UTF-8"));
} else {
temp.append(valueString);
}
}
return MD5Utils.getMD5(temp.toString()).toUpperCase();
}