找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 134|回复: 0

[php] lcobucci/jwt 刷新验证 双Token封装

[复制链接] IP属地:广东省广州市
发表于 2023-7-5 23:10:11 | 显示全部楼层 |阅读模式
为避免在使用JWT的时候,Token过期后,会自动退出系统回到登录页面,最好是采用双Token的机制;具体过程简单描述一下:
  • 用户登录,系统返回两个令牌,AccessToken和RefreshToken,AccessToken是资源访问令牌,有效期较短;RefreshToken是刷新令牌,有效期较长。
  • 用户通过自动在Header传递AccessToken。申请资源访问,直到AccessToken过期。
  • AccessToken过期后,前端自动使用RefreshToken向服务器申请新的AccessToken
  • 客户端使用新的AccessToken请求资源,直到RefreshToken失效

  1. <?php


  2. namespace utils;

  3. use Lcobucci\Clock\SystemClock;
  4. use Lcobucci\JWT\Configuration;
  5. use Lcobucci\JWT\Signer\Key\InMemory;
  6. use Lcobucci\JWT\Signer\Hmac\Sha256;
  7. use Lcobucci\JWT\UnencryptedToken;
  8. use Lcobucci\JWT\Validation\Constraint\IssuedBy;
  9. use Lcobucci\JWT\Validation\Constraint\PermittedFor;
  10. use Lcobucci\JWT\Validation\Constraint\SignedWith;
  11. use Lcobucci\JWT\Validation\Constraint\StrictValidAt;
  12. use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
  13. use think\exception\ValidateException;

  14. class Jwt
  15. {
  16.     protected $issuedBy = 'rds.server';
  17.     protected $permittedFor = 'rds.client';
  18.     protected $issuedAt;
  19.     protected $expiresAtAccess;
  20.     protected $expiresAtRefresh;
  21.     protected $secrect = 'aHR0cDovL3Jkcy5yYWlzZWluZm8uY24=';

  22.     public function __construct()
  23.     {
  24.         config('system.jwt_issued_by')          ? $this->issuedBy = config('system.jwt_issued_by') : null;
  25.         config('system.jwt_permitted_for')      ? $this->permittedFor = config('system.jwt_permitted_for') : null;
  26.         config('system.jwt_secrect')            ? $this->secrect = config('system.jwt_secrect') : null;
  27.         $this->issuedAt = new \DateTimeImmutable();
  28.         $this->expiresAtAccess = $this->issuedAt->modify(config('system.jwt_expires_at_access') ? config('system.jwt_expires_at_access') : '+1 minute');
  29.         $this->expiresAtRefresh = $this->issuedAt->modify(config('system.jwt_expires_at_refresh') ? config('system.jwt_expires_at_refresh') : '+5 minute');
  30.     }

  31.     /**
  32.      * 生成Jwt配置对象
  33.      * @return Configuration
  34.      */
  35.     private function createJwt(){
  36.         return Configuration::forSymmetricSigner(new Sha256(),InMemory::base64Encoded($this->secrect));
  37.     }

  38.     /**
  39.      * 生成Token
  40.      * @param array $bind 必须存在字段 uid
  41.      * @param string $type
  42.      * @return string
  43.      */
  44.     public function getToken(array $bind=[], $type = 'Access'){
  45.         $config = $this->createJwt();
  46.         $builder = $config->builder();
  47.         // 访问Token可以携带用户信息,刷新Token只携带用户编号
  48.         if(is_array($bind) && !empty($bind)){
  49.             foreach ($bind as $k => $v){
  50.                 $builder->withClaim($k,$v);
  51.             }
  52.             $builder->withClaim('scopes',$type == 'Access' ? 'Access' : 'Refresh');
  53.         }
  54.         $token = $builder
  55.             ->issuedBy($this->issuedBy)
  56.             ->permittedFor($this->permittedFor)
  57.             ->issuedAt($this->issuedAt)
  58.             ->canOnlyBeUsedAfter($this->issuedAt->modify('+1 second'))
  59.             ->expiresAt($type == 'Access' ? $this->expiresAtAccess : $this->expiresAtRefresh)
  60.             ->getToken($config->signer(),$config->signingKey());
  61.         return $token->toString();
  62.     }

  63.     /**
  64.      * 校验Token
  65.      * @param $token
  66.      * @return bool
  67.      */
  68.     public function verify($token){
  69.         $config = $this->createJwt();
  70.         try {
  71.             $token = $config->parser()->parse($token);
  72.             assert($token instanceof UnencryptedToken);
  73.         } catch (\Exception $e){
  74.             \think\facade\Log::error('令牌解析失败[1]:'.$e->getMessage());
  75.             return ['status'=>1,'msg'=>'令牌解析错误'];
  76.         }

  77.         // 验证签发端是否匹配
  78.         $validate_issued = new IssuedBy($this->issuedBy);
  79.         $config->setValidationConstraints($validate_issued);
  80.         $constraints = $config->validationConstraints();
  81.         try {
  82.             $config->validator()->assert($token,...$constraints);
  83.         } catch (RequiredConstraintsViolated $e){
  84.             \think\facade\Log::error('令牌验证失败[2]:' . $e->getMessage());
  85.             return ['status'=>2,'msg'=>'签发错误'];
  86.         }

  87.         //验证客户端是否匹配
  88.         $validate_permitted_for = new PermittedFor($this->permittedFor);
  89.         $config->setValidationConstraints($validate_permitted_for);
  90.         $constraints = $config->validationConstraints();
  91.         try {
  92.             $config->validator()->assert($token,...$constraints);
  93.         } catch (RequiredConstraintsViolated $e){
  94.             \think\facade\Log::error('令牌验证失败[3]:' . $e->getMessage());
  95.             return ['status'=>3,'msg'=>'客户端错误'];
  96.         }

  97.         // 验证是否过期
  98.         $timezone = new \DateTimeZone('Asia/Shanghai');
  99.         $time = new SystemClock($timezone);
  100.         $validate_exp = new StrictValidAt($time);
  101.         $config->setValidationConstraints($validate_exp);
  102.         $constraints = $config->validationConstraints();
  103.         try {
  104.             $config->validator()->assert($token,...$constraints);
  105.         } catch (RequiredConstraintsViolated $e){
  106.             \think\facade\Log::error('令牌验证失败[4]:' . $e->getMessage());
  107.             return ['status'=>4,'msg'=>'已过期'];
  108.         }

  109.         // 验证令牌是否已使用预期的签名者和密钥签名
  110.         $validate_signed = new SignedWith(new Sha256(),InMemory::base64Encoded($this->secrect));
  111.         $config->setValidationConstraints($validate_signed);
  112.         $constraints = $config->validationConstraints();
  113.         try {
  114.             $config->validator()->assert($token,...$constraints);
  115.         } catch (RequiredConstraintsViolated $e){
  116.             \think\facade\Log::error('令牌验证失败[5]:' . $e->getMessage());
  117.             return ['status'=>5,'msg'=>'签名错误'];
  118.         }

  119.         return ['status'=>0,'msg'=>'验证通过'];
  120.     }

  121.     /**
  122.      * 获取token的载体内容
  123.      * @param $token
  124.      * @return mixed
  125.      */
  126.     public function getTokenContent($token){
  127.         $config = $this->createJwt();
  128.         try {
  129.             $decode_token = $config->parser()->parse($token);
  130.             $claims = json_decode(base64_decode($decode_token->claims()->toString()),true);
  131.         } catch (\Exception $e){
  132.             throw new ValidateException($e->getMessage());
  133.         }
  134.         return $claims;
  135.     }

  136. }
复制代码
配套配置文件:config/jwt.php

  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | 系统设置
  4. // +----------------------------------------------------------------------

  5. return [
  6.     // 密码加密
  7.     'password_secrect'          => 'Rapid_Development_System',
  8.     // 是否开启验证码
  9.     'verify_status'             => false,
  10.     // JWT配置
  11.     'jwt_issued_by'             => 'rds.server',
  12.     'jwt_permitted_for'         => 'rds.client',
  13.     'jwt_secrect'               => 'aHR0cDovL3Jkcy5yYWlzZWluZm8uY24=',
  14.     'jwt_expires_at_access'     => '+5 minute',
  15.     'jwt_expires_at_refresh'    => '+30 minute',
  16.   ];
复制代码
测试类文件:jwtTest.php

  1. <?php


  2. namespace app\controller;

  3. use app\BaseController;
  4. use utils\Jwt;

  5. class JwtTest extends BaseController
  6. {
  7.     public function index()
  8.     {
  9.         return '';
  10.     }

  11.     /**
  12.      * 创建Token
  13.      * @return \think\response\Json
  14.      */
  15.     public function getToken(){
  16.         $type = $this->request->param('type','Access');
  17.         $jwt = new Jwt();
  18.         $token = $jwt->getToken(['uid'=>1],$type);
  19.         return json(['status'=>200,'data'=>$token]);
  20.     }

  21.     /**
  22.      * 提取Token内容
  23.      * @return \think\response\Json
  24.      */
  25.     public function getContent(){
  26.         $token = $this->request->header('AccessToken');
  27.         if($token){
  28.             $jwt = new Jwt();
  29.             $content = $jwt->getTokenContent($token);
  30.         } else {
  31.             $content = '无有效令牌';
  32.         }
  33.         return json(['status'=>200,'data'=>$content]);
  34.     }

  35.     /**
  36.      * 验证令牌
  37.      * @return \think\response\Json
  38.      */
  39.     public function verifyToken(){
  40.         $token = $this->request->header('AccessToken');
  41.         if($token){
  42.             $jwt = new Jwt();
  43.             $content = $jwt->verify($token);
  44.         } else {
  45.             $content = '无有效令牌';
  46.         }
  47.         return json(['status'=>200,'data'=>$content['msg']]);
  48.     }

  49.     /**
  50.      * 登录后生成访问令牌和刷新令牌
  51.      * @return \think\response\Json
  52.      */
  53.     public function getTokens(){
  54.         $jwt = new Jwt();
  55.         $payload = [
  56.             'uid' => [
  57.                 'user_id'   => 100,
  58.                 'username'  => 'Tome',
  59.                 'sex'       => 2,
  60.             ]
  61.         ];
  62.         $accessToken = $jwt->getToken($payload,'Access');
  63.         $refreshToken = $jwt->getToken(['uid'=>100],'Refresh');
  64.         $tokens = [
  65.             'Access' => $accessToken,
  66.             'Refresh'=> $refreshToken
  67.         ];
  68.         return json(['status'=>200,'data'=>$tokens]);
  69.     }


  70.     /**
  71.      * 通过刷新令牌,申请新的访问令牌
  72.      * @return \think\response\Json
  73.      */
  74.     public function refreshToken(){
  75.         $token = $this->request->header('RefreshToken');
  76.         $jwt = new Jwt();
  77.         if($jwtTools->verify($token)){
  78.             $content = $jwt->getTokenContent($token);
  79.             $accessToken = $jwt->getToken(['uid'=>$content['uid']],'Access');
  80.             $tokens = [
  81.                 'Access' => $accessToken,
  82.                 'Refresh'=> $token
  83.             ];
  84.             return json(['status'=>200,'data'=>$tokens]);
  85.         } else {
  86.             return json(['status'=>411,'data'=>'刷新令牌无效']);
  87.         }
  88.     }
  89. }
复制代码



您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|西兴社区 ( 蜀ICP备2022005627号 )|网站地图

GMT+8, 2024-12-27 13:56 , Processed in 0.675626 second(s), 21 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表