一、概述
開年第一篇,該篇主要講述了接口開發中,如何安全認證、如何用php簽名認證。
二、說說歷史
簽名認證是什么?為什么要做簽名認證?簽名認證哪里會用到?no、no、no.....是不是,是不是,一下子疑問就這么多了!沒事兒,通過追溯歷史,我們來明白這些。
1、簽名認證是什么?
2、為什么要做簽名認證?
在使用http或者soap傳輸數據的時候,簽名作為其中一個參數,可以起到關鍵作用:1、鑒權(通過客戶的密鑰,服務端的密鑰匹配);2、數據防篡改(參數是明文傳輸,將參數及密鑰加密作為簽名與服務器匹配);下面來分析下具體的方式:
將請求參數中的各個鍵值對按照key的字符串順序升序排列(大小寫敏感),把key和value拼成一串之后最后加上密鑰,組成key1value1key2value2PRIVATEKEY的格式,轉成utf-8編碼的字節序列后計算md5,作為請求的簽名。計算出來的簽名串應當全為小寫形式。如果某個參數的值為空,則此參數不參與簽名。
3、簽名認證哪里會用到?
最常見的使我們在開發接口的時候,為了不被非法訪問,往往我們會做簽名認證,比如支付接口......
然后就是第三方平台的開發,比如微信公眾平台......
三、流程分析
客戶端:首先我們為提供給用戶一份接口文檔,文檔里面我們給用戶提供api地址、簽名算法解析過程、數據說明。
服務端:進行客戶端檢驗,通常就是參數個數、格式驗證,app_key驗證(這個是博主這里使用的,這個app_key會交給用戶,用戶訪問接口的時候必須帶上該app_key),簽名結果驗證。
以我這次寫的為例子來描述一下整個流程:
用戶參照我們提供的接口文檔,請求我們的接口,用戶按照我們接口說明的簽名算法生成簽名串作為參數,然后帶上必要的參數(GET、POST),如果服務端驗證成功,則返回真實數據。
在服務端:我們首要驗證提交參數的個數、格式是否正確,然后我們通過提交的參數用簽名算法生成一個簽名串,最后服務端生成的簽名串和客戶端提交過來的簽名串進行比較,成功返回真實數據。
四、上代碼
1 <?php 2 namespace ceshi; 3 4 /** 5 * 簽名認證算法 6 * HMAC-SHA256加密方式 7 * 登錄認證加入access_token:access-token通過登錄接口去獲取,通過刷新接口去刷新,需要注意返回的過期時間,要在過期時間之前刷新重新獲取access-token。目前約定所有接口都必須傳access-token,也就是用戶必須先登錄才可以看到相關內容 8 * 隨機函數可換更好的方法-本實例隨機函數比較簡單,隨機性不夠 9 * ApiSign類作為服務端,類以外的代碼作為客戶端示例代碼 10 * author jiechengyang https://www.cnblogs.com/YangJieCheng/ 11 */ 12 class ApiSign 13 { 14 CONST DELAY_TIME = 2000; 15 CONST ACCESS_TOCEN_PATH = './access_token'; 16 17 private $_config = []; 18 protected $AppKey = 'voBVVQxfMxDmhuxV70'; 19 protected $AppSecret = 'QJF5P8qWFJakF9Ve89ZcIstHKbkt5fVA'; 20 protected $timeout = 300; 21 protected $algo = 'sha256'; 22 public $loginCheck = false; 23 24 public function __construct($config, $loginCheck) 25 { 26 $this->_config = $config; 27 empty($this->AppKey) && $this->AppKey = $this->generateRandomString(); 28 empty($this->AppSecret) && $this->AppSecret = $this->generateRangeNum(); 29 $this->loginCheck = $loginCheck; 30 if ($this->init()) { 31 echo '<span style="color:red" id="sign_success">恭喜,簽名認證成功</span><script type="text/javascript"></script>'; 32 echo <<<JS 33 <script> 34 var colors = ['#ffff00', '#ff66ff', '#99cc33', '#66ff33', '#000000', '#FF83FA', '#CAE1FF']; 35 var tmp = 0; 36 var timer = setInterval(colorChanage, 1000); 37 function colorChanage() 38 { 39 if(tmp == colors.length) { 40 tmp = 0; 41 } 42 document.getElementById('sign_success').style.color = colors[tmp++]; 43 } 44 </script> 45 JS; 46 } 47 } 48 49 protected function init() 50 { 51 if (!isset($this->_config['_key']) 52 || !isset($this->_config['_sign']) 53 || !isset($this->_config['_time']) 54 || !isset($this->_config['_nonce']) 55 || strlen($this->_config['_nonce']) !== 32 56 || !is_numeric($this->_config['_time'])) { 57 $this->callback(['message' => '請求參數不全, 或參數不規范'], 'error'); 58 } 59 60 if (!isset($this->_config['_time']) || $this->getIsTimeOut()) { 61 $this->callback(['message' => '請求超時'], 'error'); 62 } 63 64 // 客戶端驗證 65 $requestSignature = $this->_config['_sign']; 66 $requestSignature = str_replace(' ', '+', $requestSignature); 67 if($this->_config['_key'] !== $this->AppKey) { 68 $this->callback(['message' => '非法的app_key'], 'error'); 69 } 70 71 $signature = $this->generateSign($this->_config); 72 if ($requestSignature != $signature) { 73 $this->callback(['message' => '簽名錯誤'], 'error'); 74 } 75 76 $accessToken = $this->getAccessToken(); 77 78 // 用戶登錄token驗證 79 if ($this->loginCheck && $accessToken != $this->_config['access_token']) { 80 $this->callback(['message' => 'access_token錯誤'], 'error'); 81 } 82 83 return true; 84 } 85 86 private function callback($json=[], $status='ok') 87 { 88 $config = [ 89 'ok' => [200, '操作成功'], 90 'error' => [300, '操作失敗'], 91 'timeout' => [300, '操作超時'], 92 ]; 93 $json['statusCode'] = $config[$status][0]; 94 !isset($json['message']) && $json['message'] = $config[$status][1]; 95 echo json_encode($json); 96 exit; 97 } 98 99 private function getIsTimeOut() 100 { 101 if (abs($this->_config['_time'] - time()) > $this->timeout) { 102 return true; 103 } 104 105 return false; 106 } 107 108 /** 109 * 生成隨便數 110 */ 111 public function generateRangeNum($length = 32, $isToLower = false) 112 { 113 $str = $this->generateRandomString($length); 114 if ($isToLower) { 115 $str = strtolower($str); 116 } 117 118 return $str; 119 } 120 121 private function generateRandomString($length = 10) { 122 $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 123 $randomString = ''; 124 for ($i = 0; $i < $length; $i++) { 125 $randomString .= $characters[rand(0, strlen($characters) - 1)]; 126 } 127 128 return $randomString; 129 } 130 131 protected function generateSign($params) 132 { 133 if (isset($params['_sign'])) { 134 unset($params['_sign']); 135 } 136 137 if($this->loginCheck && isset($params['access_token'])) { 138 unset($params['access_token']); 139 } 140 141 ksort($params); 142 $str = ''; 143 foreach ($params as $key => $value) { 144 $str .= $key . '=' . $value . '&'; 145 } 146 147 $str = rtrim($str, '&'); 148 149 return hash_hmac($this->algo, $str, $this->AppSecret, false); 150 } 151 152 public function getAccessToken() 153 { 154 155 if (!file_exists(self::ACCESS_TOCEN_PATH)) { 156 $json = ['value' => $this->_config['access_token'], 'expires_in' => 7200, 'time' => time()]; 157 file_put_contents(self::ACCESS_TOCEN_PATH, json_encode($json)); 158 return $this->_config['access_token']; 159 } 160 161 $accessToken = json_decode(file_get_contents(self::ACCESS_TOCEN_PATH), true); 162 if (time() - $accessToken['time'] > $accessToken['expires_in'] - self::DELAY_TIME) { 163 $json = ['value' => $this->_config['access_token'], 'expires_in' => 7200, 'time' => time()]; 164 file_put_contents(self::ACCESS_TOCEN_PATH, json_encode($json)); 165 return $this->_config['access_token']; 166 } 167 168 return $accessToken['value']; 169 170 } 171 } 172 173 /** 174 * 生成隨便數 175 */ 176 function generateRangeNum($length = 32, $isToLower = false) 177 { 178 $str = generateRandomString($length); 179 if ($isToLower) { 180 $str = strtolower($str); 181 } 182 183 return $str; 184 } 185 186 function generateRandomString($length = 10) { 187 $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 188 $randomString = ''; 189 for ($i = 0; $i < $length; $i++) { 190 $randomString .= $characters[rand(0, strlen($characters) - 1)]; 191 } 192 193 return $randomString; 194 } 195 196 function generateSign($algo, $params, $AppSecret) 197 { 198 if (isset($params['_sign'])) { 199 unset($params['_sign']); 200 } 201 202 ksort($params); 203 $str = ''; 204 foreach ($params as $key => $value) { 205 $str .= $key . '=' . $value . '&'; 206 } 207 208 $str = rtrim($str, '&'); 209 210 return hash_hmac($algo, $str, $AppSecret, false); 211 } 212 213 header("content-type:text/html;charset:utf-8"); 214 $algo = 'sha256'; 215 $AppKey = 'voBVVQxfMxDmhuxV70'; 216 $AppKey = 'Rd1bW719zRbCXOBx3L';//---用於公司項目測試 217 $AppSecret = 'QJF5P8qWFJakF9Ve89ZcIstHKbkt5fVA'; 218 $AppSecret = '73ZBAnbwVPDu0dvdlYE0RMvzsbhehejd';//---用於公司項目測試 219 $nonce = generateRangeNum(32); 220 $nonce = 'kj4ESP2Qcngaj3eAjpnrhCQR9g4yKnTM';//---用於公司項目測試 221 $loginCheck = false; 222 $params = [ 223 '_key' => $AppKey, 224 '_time' => time(), 225 '_nonce' => $nonce, 226 ]; 227 $sign = generateSign($algo, $params, $AppSecret); 228 echo $params['_time'],'<hr/>';//---用於公司項目測試 229 echo $sign;exit;//---用於公司項目測試 230 $params['_sign'] = $sign; 231 232 $loginCheck && $params['access_token'] = generateRangeNum(16); 233 // echo '<pre>';print_r($params); 234 $apiSignModel = new ApiSign($params, $loginCheck);