为避免在使用JWT时,当Token过期时,系统会自动退出系统,返回登录页面。最好使用双Token机制;具体过程简单描述一下:用户登录,系统返回两个token,AccessToken和RefreshToken,AccessToken是资源访问令牌,有效期较短;RefreshToken是一种刷新令牌,有效期很长。用户自动在Header中传递AccessToken。申请资源访问,直到AccessToken过期。AccessToken过期后,前端自动使用RefreshToken向服务端申请新的AccessToken。客户端使用新的AccessToken请求资源,直到RefreshToken失效。token的具体实现稍后分享。前提供条件:PHP7.4版本及以上,lcobucci/jwt4.1.5封装类文件:utils/JwtTools.phpissuedBy=config('system.jwt_issued_by'):null;配置(的系统.jwt_permitted_for')?$this->permittedFor=config('system.jwt_permitted_for'):null;配置('system.jwt_secrect')?$this->secrect=config('system.jwt_secrect'):null;$this->issuedAt=new\DateTimeImmutable();$this->expiresAtAccess=$this->issuedAt->modify(config('system.jwt_expires_at_access')?config('system.jwt_expires_at_access'):'+1分钟');$this->expiresAtRefresh=$this->issuedAt->modify(config('system.jwt_expires_at_refresh')?config('system.jwt_expires_at_refresh'):'+5分钟');}/***生成Jwt配置对象*@returnConfiguration*/privatefunctioncreateJwt(){returnConfiguration::forSymmetricSigner(newSha256(),InMemory::base64Encoded($this->secrect));}/***生成Token*@paramarray$bind必须存在字节段uid*@paramstring$type*@returnstring*/publicfunctiongetToken(array$bind=[],$type='Access'){$config=$this->createJwt();$builder=$config->builder();//AccessToken可以携带用户信息,刷新Token只携带用户IDif(is_array($bind)&&!empty($bind)){foreach($bindas$k=>$v){$builder->withClaim($k,$v);}$builder->withClaim('scopes',$type=='Access'?'Access':'Refresh');}$token=$builder->issuedBy($this->issuedBy)->permittedFor($this->permittedFor)->issuedAt($this->issuedAt)->canOnlyBeUsedAfter($this->issuedAt->modify('+1秒'))->expiresAt($type=='Access'?$this->expiresAtAccess:$this->expiresAtRefresh)->getToken($config->signer(),$config->signingKey());返回$token->toString();}/***验证令牌*@param$token*@returnbool*/publicfunctionverify($token){$config=$this->createJwt();尝试{$token=$config->parser()->parse($token);assert($tokeninstanceofUnencryptedToken);}catch(\Exception$e){\think\facade\Log::error('令牌解析失败[1]:'.$e->getMessage());return['status'=>1,'msg'=>'Token解析错误'];}//验证发行者是否匹配$validate_issued=newIssuedBy($this->issuedBy);$config->setValidationConstraints($validate_issued);$constraints=$config->validationConstraints();尝试{$config->validator()->assert($token,...$constraints);}catch(RequiredConstraintsViolated$e){\think\facade\Log::error('令牌验证失败[2]:'.$e->getMessage());return['status'=>2,'msg'=>'发卡错误'];}//验证客户端是否匹配$validate_permitted_for=newPermittedFor($this->permittedFor);$config->setValidationConstraints($validate_permitted_for);$constraints=$config->validationConstraints();尝试{$config->validator()->assert($token,...$constraints);}catch(RequiredConstraintsViolated$e){\think\facade\Log::error('令牌验证失败[3]:'.$e->getMessage());return['status'=>3,'msg'=>'客户端错误'];}//验证是否过期$timezone=new\DateTimeZone('Asia/Shanghai');$time=newSystemClock($timezone);$validate_exp=newStrictValidAt($time);$config->setValidationConstraints($validate_exp);$constraints=$config->validationConstraints();尝试{$config->validator()->assert($token,...$constraints);}catch(RequiredConstraintsViolated$e){\think\facade\Log::error('令牌验证失败[4]:'.$e->getMessage());返回['status'=>4,'msg'=>'Expired'];}//验证令牌是否具有预期的签名者和密钥签名$validate_signed=newSignedWith(newSha256(),InMemory::base64Encoded($this->secrect));$config->setValidationConstraints($validate_signed);$constraints=$config->validationConstraints();尝试{$config->validator()->assert($token,...$constraints);}catch(RequiredConstraintsViolated$e){\think\facade\Log::error('令牌验证失败[5]:'.$e->getMessage());return['status'=>5,'msg'=>'签名错误'];}return['status'=>0,'msg'=>'验证通过'];}/***获取token的载体内容*@param$token*@returnmixed*/publicfunctiongetTokenContent($token){$config=$this->createJwt();尝试{$decode_token=$config->parser()->parse($token);$claims=json_decode(base64_decode($decode_token->claims()->toString()),true);}catch(\Exception$e){thrownewValidateException($e->getMessage());}返回$claims;}}匹配配置文件:config/system.php'Rapid_Development_System',//是否开启验证码'verify_status'=>false,//JWT配置'jwt_issued_by'=>'rds.server','jwt_permitted_for'=>'rds.client','jwt_secrect'=>'aHR0cDovL3Jkcy5yYWlzZWluZm8uY24=','jwt_expires_at_access'=>'+5分钟','jwt_expires_at_refresh'=>'+30分钟'测试文件,];:app/admin/controller/JwtTest.phprequest->param('type','Access');$jwtTools=newJwtTools();$token=$jwtTools->getToken(['uid'=>1],$type);返回json(['status'=>200,'data'=>$token]);}/***提取令牌内容*@return\think\response\Json*/publicfunctiongetContent(){$token=$this->request->header('AccessToken');if($token){$jwtTools=newJwtTools();$content=$jwtTools->getTokenContent($token);}else{$content='无有效令牌';}返回json(['status'=>200,'data'=>$content]);}/***验证指令牌*@return\think\response\Json*/publicfunctionverifyToken(){$token=$this->request->header('AccessToken');if($token){$jwtTools=newJwtTools();$content=$jwtTools->verify($token);}else{$content='无有效令牌';}返回json(['status'=>200,'data'=>$content['msg']]);}/***登录后生成访问令牌和刷新令牌*@return\think\response\Json*/publicfunctiongetTokens(){$jwtTools=newJwtTools();$payload=['uid'=>['user_id'=>100,'username'=>'Tome','sex'=>2,]];$accessToken=$jwtTools->getToken($payload,'Access');$refreshToken=$jwtTools->getToken(['uid'=>100],'刷新');$tokens=['Access'=>$accessToken,'Refresh'=>$refreshToken];返回json(['status'=>200,'data'=>$tokens]);}/***通过刷新令牌申请新的访问令牌*@return\think\response\Json*/publicfunctionrefreshToken(){$token=$this->request->header('RefreshToken');$jwtTools=newJwtTools();if($jwtTools->verify($token)){$content=$jwtTools->getTokenContent($令牌);$accessToken=$jwtTools->getToken(['uid'=>$content['uid']],'Access');$tokens=['Access'=>$accessToken,'Refresh'=>$token];返回json(['status'=>200,'data'=>$tokens]);}else{returnjson(['status'=>411,'data'=>'refreshtokeninvalid']);}}}通过POSTMAN软件,调用测试接口即可!
