|
为避免在使用JWT的时候,Token过期后,会自动退出系统回到登录页面,最好是采用双Token的机制;具体过程简单描述一下: - 用户登录,系统返回两个令牌,AccessToken和RefreshToken,AccessToken是资源访问令牌,有效期较短;RefreshToken是刷新令牌,有效期较长。
- 用户通过自动在Header传递AccessToken。申请资源访问,直到AccessToken过期。
- AccessToken过期后,前端自动使用RefreshToken向服务器申请新的AccessToken
- 客户端使用新的AccessToken请求资源,直到RefreshToken失效
- <?php
- namespace 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 Jwt
- {
- 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/jwt.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',
- ];
复制代码 测试类文件:jwtTest.php
- <?php
- namespace app\controller;
- use app\BaseController;
- use utils\Jwt;
- class JwtTest extends BaseController
- {
- public function index()
- {
- return '';
- }
- /**
- * 创建Token
- * @return \think\response\Json
- */
- public function getToken(){
- $type = $this->request->param('type','Access');
- $jwt = new Jwt();
- $token = $jwt->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){
- $jwt = new Jwt();
- $content = $jwt->getTokenContent($token);
- } else {
- $content = '无有效令牌';
- }
- return json(['status'=>200,'data'=>$content]);
- }
- /**
- * 验证令牌
- * @return \think\response\Json
- */
- public function verifyToken(){
- $token = $this->request->header('AccessToken');
- if($token){
- $jwt = new Jwt();
- $content = $jwt->verify($token);
- } else {
- $content = '无有效令牌';
- }
- return json(['status'=>200,'data'=>$content['msg']]);
- }
- /**
- * 登录后生成访问令牌和刷新令牌
- * @return \think\response\Json
- */
- public function getTokens(){
- $jwt = new Jwt();
- $payload = [
- 'uid' => [
- 'user_id' => 100,
- 'username' => 'Tome',
- 'sex' => 2,
- ]
- ];
- $accessToken = $jwt->getToken($payload,'Access');
- $refreshToken = $jwt->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');
- $jwt = new Jwt();
- if($jwtTools->verify($token)){
- $content = $jwt->getTokenContent($token);
- $accessToken = $jwt->getToken(['uid'=>$content['uid']],'Access');
- $tokens = [
- 'Access' => $accessToken,
- 'Refresh'=> $token
- ];
- return json(['status'=>200,'data'=>$tokens]);
- } else {
- return json(['status'=>411,'data'=>'刷新令牌无效']);
- }
- }
- }
复制代码
|
|