为防止在应用JWT的时候,Token过期后,会主动退出零碎回到登录页面,最好是采纳双Token的机制;具体过程简略形容一下:

  1. 用户登录,零碎返回两个令牌,AccessToken和RefreshToken,AccessToken是资源拜访令牌,有效期较短;RefreshToken是刷新令牌,有效期较长。
  2. 用户通过主动在Header传递AccessToken。申请资源拜访,直到AccessToken过期。
  3. AccessToken过期后,前端主动应用RefreshToken向服务器申请新的AccessToken
  4. 客户端应用新的AccessToken申请资源,直到RefreshToken生效

Jwt 4.0以上版本的封装网上的参考比拟少,在这里,提供一份简略的封装,至于双令牌的具体实现,前面再陆续分享。

前提条件:PHP 7.4版本及以上,lcobucci/jwt 4.1.5

封装类文件:utils/JwtTools.php

<?phpnamespace utils;use Lcobucci\Clock\SystemClock;use Lcobucci\JWT\Configuration;use Lcobucci\JWT\Signer\Key\InMemory;use Lcobucci\JWT\Signer\Hmac\Sha256;use Lcobucci\JWT\UnencryptedToken;use Lcobucci\JWT\Validation\Constraint\IssuedBy;use Lcobucci\JWT\Validation\Constraint\PermittedFor;use Lcobucci\JWT\Validation\Constraint\SignedWith;use Lcobucci\JWT\Validation\Constraint\StrictValidAt;use Lcobucci\JWT\Validation\RequiredConstraintsViolated;use think\exception\ValidateException;class JwtTools{    protected $issuedBy = 'rds.server';    protected $permittedFor = 'rds.client';    protected $issuedAt;    protected $expiresAtAccess;    protected $expiresAtRefresh;    protected $secrect = 'aHR0cDovL3Jkcy5yYWlzZWluZm8uY24=';    public function __construct()    {        config('system.jwt_issued_by')          ? $this->issuedBy = config('system.jwt_issued_by') : null;        config('system.jwt_permitted_for')      ? $this->permittedFor = config('system.jwt_permitted_for') : null;        config('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 minute');        $this->expiresAtRefresh = $this->issuedAt->modify(config('system.jwt_expires_at_refresh') ? config('system.jwt_expires_at_refresh') : '+5 minute');    }    /**     * 生成Jwt配置对象     * @return Configuration     */    private function createJwt(){        return Configuration::forSymmetricSigner(new Sha256(),InMemory::base64Encoded($this->secrect));    }    /**     * 生成Token     * @param array $bind 必须存在字段 uid     * @param string $type     * @return string     */    public function getToken(array $bind=[], $type = 'Access'){        $config = $this->createJwt();        $builder = $config->builder();        // 拜访Token能够携带用户信息,刷新Token只携带用户编号        if(is_array($bind) && !empty($bind)){            foreach ($bind as $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 second'))            ->expiresAt($type == 'Access' ? $this->expiresAtAccess : $this->expiresAtRefresh)            ->getToken($config->signer(),$config->signingKey());        return $token->toString();    }    /**     * 校验Token     * @param $token     * @return bool     */    public function verify($token){        $config = $this->createJwt();        try {            $token = $config->parser()->parse($token);            assert($token instanceof UnencryptedToken);        } catch (\Exception $e){            \think\facade\Log::error('令牌解析失败[1]:'.$e->getMessage());            return ['status'=>1,'msg'=>'令牌解析谬误'];        }        // 验证签发端是否匹配        $validate_issued = new IssuedBy($this->issuedBy);        $config->setValidationConstraints($validate_issued);        $constraints = $config->validationConstraints();        try {            $config->validator()->assert($token,...$constraints);        } catch (RequiredConstraintsViolated $e){            \think\facade\Log::error('令牌验证失败[2]:' . $e->getMessage());            return ['status'=>2,'msg'=>'签发谬误'];        }        //验证客户端是否匹配        $validate_permitted_for = new PermittedFor($this->permittedFor);        $config->setValidationConstraints($validate_permitted_for);        $constraints = $config->validationConstraints();        try {            $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 = new SystemClock($timezone);        $validate_exp = new StrictValidAt($time);        $config->setValidationConstraints($validate_exp);        $constraints = $config->validationConstraints();        try {            $config->validator()->assert($token,...$constraints);        } catch (RequiredConstraintsViolated $e){            \think\facade\Log::error('令牌验证失败[4]:' . $e->getMessage());            return ['status'=>4,'msg'=>'已过期'];        }        // 验证令牌是否已应用预期的签名者和密钥签名        $validate_signed = new SignedWith(new Sha256(),InMemory::base64Encoded($this->secrect));        $config->setValidationConstraints($validate_signed);        $constraints = $config->validationConstraints();        try {            $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     * @return mixed     */    public function getTokenContent($token){        $config = $this->createJwt();        try {            $decode_token = $config->parser()->parse($token);            $claims = json_decode(base64_decode($decode_token->claims()->toString()),true);        } catch (\Exception $e){            throw new ValidateException($e->getMessage());        }        return $claims;    }} 

配套配置文件:config/system.php

<?php// +----------------------------------------------------------------------// | 零碎设置// +----------------------------------------------------------------------return [    // 明码加密    'password_secrect'          => '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 minute',    'jwt_expires_at_refresh'    => '+30 minute',  ]; 

测试类文件:app/admin/controller/JwtTest.php

<?phpnamespace app\admin\controller;use app\BaseController;use utils\JwtTools;class JwtTest extends BaseController{    public function index()    {        return '您好!这是一个[admin]示例利用';    }    /**     * 创立Token     * @return \think\response\Json     */    public function getToken(){        $type = $this->request->param('type','Access');        $jwtTools = new JwtTools();        $token = $jwtTools->getToken(['uid'=>1],$type);        return json(['status'=>200,'data'=>$token]);    }    /**     * 提取Token内容     * @return \think\response\Json     */    public function getContent(){        $token = $this->request->header('AccessToken');        if($token){            $jwtTools = new JwtTools();            $content = $jwtTools->getTokenContent($token);        } else {            $content = '无无效令牌';        }        return json(['status'=>200,'data'=>$content]);    }    /**     * 验证令牌     * @return \think\response\Json     */    public function verifyToken(){        $token = $this->request->header('AccessToken');        if($token){            $jwtTools = new JwtTools();            $content = $jwtTools->verify($token);        } else {            $content = '无无效令牌';        }        return json(['status'=>200,'data'=>$content['msg']]);    }    /**     * 登录后生成拜访令牌和刷新令牌     * @return \think\response\Json     */    public function getTokens(){        $jwtTools = new JwtTools();        $payload = [            'uid' => [                'user_id'   => 100,                'username'  => 'Tome',                'sex'       => 2,            ]        ];        $accessToken = $jwtTools->getToken($payload,'Access');        $refreshToken = $jwtTools->getToken(['uid'=>100],'Refresh');        $tokens = [            'Access' => $accessToken,            'Refresh'=> $refreshToken        ];        return json(['status'=>200,'data'=>$tokens]);    }    /**     * 通过刷新令牌,申请新的拜访令牌     * @return \think\response\Json     */    public function refreshToken(){        $token = $this->request->header('RefreshToken');        $jwtTools = new JwtTools();        if($jwtTools->verify($token)){            $content = $jwtTools->getTokenContent($token);            $accessToken = $jwtTools->getToken(['uid'=>$content['uid']],'Access');            $tokens = [                'Access' => $accessToken,                'Refresh'=> $token            ];            return json(['status'=>200,'data'=>$tokens]);        } else {            return json(['status'=>411,'data'=>'刷新令牌有效']);        }    }} 

通过POSTMAN软件,调用测试接口即可!