1. 程式人生 > 其它 >PHP萬能token類--純原生

PHP萬能token類--純原生

技術標籤:PHP資料校驗phpredis其他經驗分享

所有的專案中都會涉及到身份鑑權的使用,而目前最常用的是通過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儲存的資訊