PHP 实现Token无状态登录验证,无需服务端存储,降低服务器压力

By | 2020年5月16日

早之前大家常用的登录方式是客户端传输账号密码,后台通过比对后成功了,客户端存储在cookie中,每次刷新页面或请求服务端,服务端都需要从数据库中调取对比信息;对于服务端来说,对比登录状态是服务端请求最频繁的动作,所以如果每次都调用数据库请求的话,会降低服务端相应速度,增加服务端的压力。

所以现在很多项目开始使用token验证,其中token验证中有一种是无状态验证,那么什么是无状态呢?

1、服务端不保存任何客户端请求者信息;

2、客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份。

无状态有什么有点?

1、客户端请求不依赖服务端的信息,任何多次请求不需要必须访问到同一台服务;
2、服务端的集群和状态对客户端透明;
3、服务端可以任意的迁移和伸缩;
4、减小服务端存储压力。

实现代码(PHP):

    /**
     *	生成token
     */
    public function setToken($name,$id){
        //用户名、此时的时间戳,并将过期时间拼接在一起
		$admin = $name; //获取前台传来的用户账号
		$time = time();
		$aging = 86400;
		$end_time = time()+$aging;
		$tTr=$this->number_encrypt($time);  //自定义方法(给数字进行加密,可解密,下方有实例)
		$tEtr=$this->number_encrypt($end_time);
		$idS=$this->number_encrypt($id);
		$info = $admin. '.' . $id . '.' .$time.'.'.$end_time;
		$infoE= base64_encode($admin). '.' . $idS . '.' .$tTr.'.'.$tEtr;
		//根据以上信息信息生成签名(密钥为 siasqr)
		$signature = hash_hmac('md5',$info,'siasqr');
		//最后将这两部分拼接起来,得到最终的Token字符串
		$token = $infoE . '.' . $signature;
		return [
			'token'=>$token,
			'name'=>$admin,
			'id'=>$id,
			'aging'=>$aging+10
		];
    }
    /**
     *	数字自定义加密
     */
	public function number_encrypt($n){
		$dictionary=['0'=>'aZ','1'=>'Bw','2'=>'cYq','3'=>'Dx','4'=>'eV','5'=>'FvU','6'=>'gT','7'=>'Hs','8'=>'iRp','9'=>'Jo','.'=>'kL'];
		$nS=(string)$n;
		$nA=str_split($nS);
		$r='';
		foreach ($nA as $val) {
			$r.=isset($dictionary[$val])?$dictionary[$val]:'';
		}
		return $r;
	}
	/**
     *	数字解密
     */
	public function number_decrypt($s){
		$dictionary=[0=>'/aZ/',1=>'/Bw/',2=>'/cYq/',3=>'/Dx/',4=>'/eV/',5=>'/FvU/',6=>'/gT/',7=>'/Hs/',8=>'/iRp/',9=>'/Jo/',10=>'/kL/'];
		$rp=[0=>'0',1=>'1',2=>'2',3=>'3',4=>'4',5=>'5',6=>'6',7=>'7',8=>'8',9=>'9',10=>'.'];
		$r=preg_replace($dictionary,$rp,$s);
		return $r;
	}
/**
     *	检查token
     */
    public function check_token($token)
	{
		/**** api传来的token ****/
		if(!isset($token) || empty($token))
		{
			$msg['error']=1;
            $msg['msg']='Illegal request';
            return $msg;
		}
		//对比token
		$explode = explode('.',$token);//以.分割token为数组
		if(!empty($explode[0]) && !empty($explode[1]) && !empty($explode[2]) && !empty($explode[3]) && !empty($explode[4]))
		{
			$stTime=$this->number_decrypt($explode[2]);
			$edTime=$this->number_decrypt($explode[3]);
			$name=base64_decode($explode[0]);
			$id=$this->number_decrypt($explode[1]);
			if($edTime-$stTime>604800){
				$msg['error']=1;
	            $msg['msg']='Illegal request';
	            return $msg;
			}
			if($stTime>time()){
				$msg['error']=1;
	            $msg['msg']='Illegal request';
	            return $msg;
			}
			$info = $name.'.'.$id.'.'.$stTime.'.'.$edTime;//信息部分
	        $true_signature = hash_hmac('md5',$info,'siasqr');//正确的签名
			if(time() > $this->number_decrypt($explode[3]))
			{
				$msg['error']=1;
	            $msg['msg']='Token已过期,请重新登录';
	            return $msg;
			}
			if ($true_signature == $explode[4])
			{
				//检查是否有刷新
				$rf=$this->refresh_token(['name'=>$name,'id'=>$id,'stTime'=>$stTime]);
				if($rf==false){
					$msg['name']=$name;
				    $msg['id']=$id;
				    $msg['rfs']=0;
		            return $msg;
				}
				$msg['aging']=$rf['aging'];
				$msg['token']=$rf['token'];
			    $msg['name']=$name;
			    $msg['id']=$id;
			    $msg['rfs']=1;
	            return $msg;
			}
			else
			{
			    $msg['error']=1;
	            $msg['msg']='Signature failed';
	            return $msg;
			}
		}
		else
		{
            $msg['error']=1;
            $msg['msg']='Signature failed';
            return $msg;
		}
		
	}
/**
     *	刷新token
     */
	private function refresh_token($exToken){
		if(time()-$exToken['stTime']>=420){
			return $this->setToken($exToken['name'],$exToken['id']);
		}
		return false;
	}

结语:以上代码亲测可用,但不是最优,如您有更好的方式方法,欢迎一起交流。

发表评论

电子邮件地址不会被公开。 必填项已用*标注