PHP萬能token類--純原生
阿新 • • 發佈:2020-12-12
所有的專案中都會涉及到身份鑑權的使用,而目前最常用的是通過token(令牌)進行鑑權;
token的解析:(來源於:https://www.jianshu.com/p/24825a2683e6)
1、Token的引入:Token是在客戶端頻繁向服務端請求資料,服務端頻繁的去資料庫查詢使用者名稱和密碼並進行對比,判斷使用者名稱和密碼正確與否,並作出相應提示,在這樣的背景下,Token便應運而生。
2、Token的定義:Token是服務端生成的一串字串,以作客戶端進行請求的一個令牌,當第一次登入後,伺服器生成一個Token便將此Token返回給客戶端,以後客戶端只需帶上這個Token前來請求資料即可,無需再次帶上使用者名稱和密碼。
3、使用Token的目的:Token的目的是為了減輕伺服器的壓力,減少頻繁的查詢資料庫,使伺服器更加健壯。
為了更好在各專案中適用,下面將貼出一個token類,此類不僅支援校驗,還支援多使用者的登入校驗,以便一個使用者在多個客戶端的鑑權,不多說,程式碼如下:
<?php namespace app\api\controller; /** * 拿來即用的token類 * user Yang * QQ 952323608 * wechat ygy952323608 * tip:此類與傳統的token有點不一樣,建立token時會返回一個值,一個是tokenname 一個是onlykey 檢查是否有效時,需要兩個值都進行傳入 */ class Token extends Common { private $redis; private $maxNum = 3;//可同時線上人數 private $continueLife = false;//在檢查token有效後,是否自動延長token的有效期 private $resetKey = true;//在檢查有效後,是否自動將其移動到佇列末端 private $config = array( 'tokenTiming'=>3600*24*14,//token有效期 'secret'=>'Hkasnu765./HBViasp;.12',//祕鑰請勿洩露 'tokenPrefix'=>'tokenOnlyYang',//token字首,保證redis的key值不被覆蓋 此項請勿隨意修改,否則會影響 destructToken 方法的有效性 ); private $randLength = 35;//獲取token時,onlykey的長度 //public $errTip = false;//擠退是否進行提醒---待開發 被擠退後對 onlykey 進行一定時間內的快取,儲存被擠退的狀態 /** * 可闖入config設定項來區別不同型別的 token 避免token重名 */ public function __construct($config=[]) { $this->redis = getRedis(); if ($this->maxNum<=0 || !is_numeric($this->maxNum)){ $this->maxNum = 1; } if ($this->maxNum>=20){ $this->maxNum = 20; } if (!empty($config)){ if (isset($config['maxNum'])){ $this->maxNum = $config['maxNum']; } if (isset($config['continueLife'])){ $this->continueLife = $config['continueLife']; } if (isset($config['resetKey'])){ $this->resetKey = $config['resetKey']; } if (isset($config['config'])){ $this->config = $config['config']; } if (isset($config['randLength'])){ $this->randLength = $config['randLength']; } } } /** * 獲取當前redis連線柄 */ public function getRedisConnect(){ return $this->redis; } /** * 獲取指定唯一值的token值 */ private function tokenName($onlyKey){ return ($this->config['tokenPrefix']).$onlyKey.'_token'; } /** * 建立token * @param string $onlykey 建立的唯一值,保證每個使用者對應的唯一性 如uid */ public function createToken($onlyKey){ $tokenName = $this->tokenName($onlyKey); $randStr = md5(getRandomStr($this->randLength,false).$this->config['secret']);//隨機字元,用於擠兌下線 $this->checkTokenLife($tokenName); $token_info = $this->redis->get($tokenName); $token_list = empty($token_info)?[]:json_decode($token_info,true); $num = count($token_list); $token_list[$num]['lifetime'] = time()+$this->config['tokenTiming']; $token_list[$num]['randstr'] = $randStr; $this->redis->set($tokenName,json_encode($token_list),$this->config['tokenTiming']); $this->checkMaxOnline($tokenName); $this->checkMaxLife($tokenName); return array( 'token'=>$tokenName, 'onlykey'=>$randStr ); } /** * 處理token的過期資料 * @param string $tokenName 令牌名稱 */ private function checkTokenLife($tokenName){ $token_strlist = array(); $list_str = $this->redis->get($tokenName); !empty($list_str) && $token_strlist = json_decode($list_str,true); foreach ($token_strlist as $tk=>$tv){ if ($tv['lifetime']<time()){ unset($token_strlist[$tk]); } } $token_strlist = array_values($token_strlist); if (empty($token_strlist)){ $this->redis->del($tokenName); }else{ $this->redis->set($tokenName,json_encode($token_strlist)); $this->checkMaxLife($tokenName); } return true; } /** * 檢查同時線上人數限制 * @param string $tokenName 檢查的tokenname */ private function checkMaxOnline($tokenName){ $token_result = $this->redis->get($tokenName); $token_list = empty($token_result)?[]:json_decode($token_result,true); while (count($token_list)>$this->maxNum){ //大於最多允許線上人數 array_shift($token_list); } $new_list = array_values($token_list); $this->redis->set($tokenName,json_encode($new_list)); return true; } /** * 檢查token及onlykey是否合法 * @param string $token * @param string $onlykey createToken處返回的onlykey * @param string $getInfo 是否同時返回資料 * @return bool/array */ public function checkTokenStatus($tokenName,$randStr,$getInfo=false){ $this->checkMaxOnline($tokenName); $this->checkTokenLife($tokenName); $token_rs = $this->redis->get($tokenName); if (empty($token_rs)){ return false; } $token_list = empty($token_rs)?[]:json_decode($token_rs,true); foreach ($token_list as $tk=>$tv){ if ($tv['randstr'] == $randStr){ $this->continueLife === true && $token_list[$tk]['lifetime'] = time()+$this->config['tokenTiming'];//自動延期 if ($this->resetKey === true){ //重置key $token_list[count($token_list)] = $token_list[$tk]; unset($token_list[$tk]); $token_list = array_values($token_list); } $this->redis->set($tokenName,json_encode($token_list)); $this->checkMaxLife($tokenName); if ($getInfo === false){ return true; }else{ $info = !isset($tv['info'])||empty($tv['info'])?[]:json_decode($tv['info'],true); return $info; } } } return false; } /** * 設定資料 * @param string $tokenName token名 * @param string $onlyKey 唯一標識 * @param string $key 設定key * @param string/array $value 設定value * @param bool $allset 是否所有token 列表的所有者都設定 * @return bool */ public function setTokenInfo($tokenName,$onlyKey,$key,$value,$allset=true){ if ($key == 'lifetime' || $key == 'randstr'){ return false;//配置項不可修改 } $old_continueLife = $this->continueLife; $old_resetKey = $this->resetKey; $this->continueLife = false; $this->resetKey = false; $status = $this->checkTokenStatus($tokenName,$onlyKey,false); $this->continueLife = $old_continueLife; $this->resetKey = $old_resetKey; if ($status === true){ $token_list = json_decode($this->redis->get($tokenName),true); foreach ($token_list as $tk=>$tv){ if ($allset == true){ $token_list[$tk][$key] = $value; }else{ if ($tv['randstr'] == $onlyKey){ $token_list[$tk][$key] = $value; break; } } } $this->redis->set($tokenName,json_encode($token_list)); $this->checkMaxLife($tokenName); return true; }else{ return false; } } /** * 刪除資料 * @param string $tokenName token名 * @param string $onlyKey 唯一標識 * @param string $key 設定key * @param bool $allset 是否所有token 列表的所有者都刪除 * @return bool */ public function removeTokenInfo($tokenName,$onlyKey,$key,$allset=true){ if ($key == 'lifetime' || $key == 'randstr'){ return false;//配置項不可修改 } $old_continueLife = $this->continueLife; $old_resetKey = $this->resetKey; $this->continueLife = false; $this->resetKey = false; $status = $this->checkTokenStatus($tokenName,$onlyKey,false); $this->continueLife = $old_continueLife; $this->resetKey = $old_resetKey; if ($status === true){ $token_list = json_decode($this->redis->get($tokenName),true); foreach ($token_list as $tk=>$tv){ if ($allset == true){ unset($token_list[$tk][$key]); }else{ if ($tv['randstr'] == $onlyKey){ unset($token_list[$tk][$key]); break; } } } $this->redis->set($tokenName,json_encode($token_list)); $this->checkMaxLife($tokenName); return true; }else{ return false; } } /** * 獲取資料 * @param string $tokenName token名 * @param string $onlyKey 唯一標識 * @param string $key 獲取key * @return string */ public function getTokenInfo($tokenName,$onlyKey,$key){ $old_continueLife = $this->continueLife; $old_resetKey = $this->resetKey; $this->continueLife = false; $this->resetKey = false; $status = $this->checkTokenStatus($tokenName,$onlyKey,false); $this->continueLife = $old_continueLife; $this->resetKey = $old_resetKey; if ($status === true){ $token_list = json_decode($this->redis->get($tokenName),true); foreach ($token_list as $tk=>$tv){ if ($tv['randstr'] == $onlyKey){ return isset($tv[$key])?$tv[$key]:''; } } $this->redis->set($tokenName,json_encode($token_list)); $this->checkMaxLife($tokenName); return true; }else{ return false; } } /** * 檢查token的最長週期 */ private function checkMaxLife($tokenName){ $token_rs = $this->redis->get($tokenName); if (empty($token_rs)){ return true; } $token_list = json_decode($token_rs,true); $max_lifetime = 0; foreach ($token_list as $tk=>$tv){ if ($tv['lifetime']>$max_lifetime){ $max_lifetime = $tv['lifetime']-time(); } } if ($max_lifetime<=0){ $this->redis->del($tokenName); }else{ $this->redis->set($tokenName,json_encode($token_list),$max_lifetime); } return true; } /** * 設定配置項 */ public function setConfig($key,$value){ $this->$key = $value; } /** * 銷燬token 修改密碼時使用 */ public function destructToken($tokenName){ $this->redis->del($tokenName); } } //使用方法 $uid = 1000; $obj = new Token(); $tokenArr = $obj->createToken($uid);//新建token 必須保證uid是唯一的,如使用者id $obj->checkTokenStatus($tokenArr['token'],$tokenArr['onlykey'],true);//檢查token是否有效,無效為false 有效則返回true或token儲存的資訊