解决使用jwt刷新token带来的问题

先后端分离,使用token的方式校验用户信息,我选择了jwt,使用的教程在网上能够找到不少,不作介绍。php

这里说明一个使用过程当中,最重要的的一个环节刷新token带来的问题。前端

业务要达到的目标:git

用户登陆一次以后,前端保存token,后面每次向后端请求的时候,header都带上authorization信息,后端从请求中解析token,根据token验证用户信息,返回相应的信息。github

相信大部分看过文档并开始使用的同窗都已经走通到这里了,下面是入坑的开始:redis

1. 产品要求json

半个月内免登录,这里就要使用到了refreshToken了,jwt设计思想很到位:设置发给前端的token一个有效期,好比2个小时,2个小时候前端发来的token就会失效,这个时候咱们根据发来的token判断下,若是这个token在2个小时外,并在刷新token的有效期内(好比半个月内),那么咱们在给前端返回数据的时候返回一个新token,前端接到这个token存储起来,当再次请求的时候,发送新的token,如此周而复始,只要你在半个月内没有间断去进入系统,那么彻底不须要去进行登陆的操做。

2. 问题
1)如何将新的token发给前端比较好?后端

这个问题答案简单,在response 的header中设置authorization。
   关键点:后端通常使用的域名是二级域名好比个人是api.xx.com,会和前端产生一个跨域的影响,请记得必定要设置
   `$response->headers->set('Access-Control-Expose-Headers', 'Authorization');`
   设置跨域的时候还要设置一个Cache-Control,这个东西出现的问题真的是莫名其妙,坑了我好久..
   `$response->headers->set('Cache-Control', 'no-store'); // 无的话会致使前端从缓存获取头token`

2) 通常是在中间件中刷新token,当前请求继续走,如何在controller中须要根据token调取登陆用户信息?api

一会儿可能没说明问题,简单理解为:token已经刷新了,那么当前token确定失效了,继续在controller利用请求中的token确定会报token失效的错误,这里须要将新token带到后面的程序处理中,我这里更改了当前请求头,将newToken替换了request header中的Authorization。

3) 并发请求。也就是2个小时候以后,同一个页面发来了2个请求,这个很正常,好比一个请求列表数据,一个请求搜索的表单,由于token都已失效,那么难道返回2个新的token回去?跨域

这个问题找了在github里面看到了issue可是无人回答,jwt确定不会发两个新的token回去的,那麽确定会有一个token不只是失效了,刷新当前token以后,产生新的token,旧token加入到了backlist中了,没法使用,那么另一个请求天然没法成功。我这里使用Redis解决的,将旧token做为键,新token做为值,设置一个30秒过时的时间。当第二个请求来的时候,已经知道token在backlist中了,咱们能够去redis查询下是否存在这么个旧token,存在的话放行。

3. 关键中间件代码缓存

<?php
   
   namespace App\Http\Middleware;
   
   use Closure;
   use JWTAuth;
   use Tymon\JWTAuth\Exceptions\JWTException;
   use Tymon\JWTAuth\Exceptions\TokenExpiredException;
   use Tymon\JWTAuth\Exceptions\TokenInvalidException;
   use Illuminate\Support\Facades\Redis;
   
   class GetUserFromToken
   {
       
       public function handle($request, Closure $next)
       {
           $newToken = null;
           $auth = JWTAuth::parseToken();
           if (! $token = $auth->setRequest($request)->getToken()) {
               return response()->json([
                   'code' => '2',
                   'msg' => '无参数token',
                   'data' => '',
               ]);
           }
   
           try {
               $user = $auth->authenticate($token);
               if (! $user) {
                   return response()->json([
                       'code' => '2',
                       'msg' => '未查询到该用户信息',
                       'data' => '',
                    ]);
               }
               $request->headers->set('Authorization','Bearer '.$token);
           } catch (TokenExpiredException $e) {
               try {
                   sleep(rand(1,5)/100);
                   $newToken = JWTAuth::refresh($token);
                   $request->headers->set('Authorization','Bearer '.$newToken); // 给当前的请求设置性的token,以备在本次请求中须要调用用户信息
                   // 将旧token存储在redis中,30秒内再次请求是有效的
                   Redis::setex('token_blacklist:'.$token,30,$newToken);
               } catch (JWTException $e) {
                   // 在黑名单的有效期,放行
                   if($newToken = Redis::get('token_blacklist:'.$token)){
                       $request->headers->set('Authorization','Bearer '.$newToken); // 给当前的请求设置性的token,以备在本次请求中须要调用用户信息
                       return $next($request);
                   }
                   // 过时用户
                   return response()->json([
                       'code' => '2',
                       'msg' => '帐号信息过时了,请从新登陆',
                   ]);
               }
           } catch (JWTException $e) {
               return response()->json([
                   'code' => '2',
                   'msg' => '无效token',
                   'data' => '',
                ]);
           }
           $response = $next($request);
   
           if ($newToken) {
               $response->headers->set('Authorization', 'Bearer '.$newToken);
           }
   
           return $response;
       }
   }

一成天的时间耗在这里了,实践才会发现问题,累并快乐着解决了^_^

=====================割了一了白了(2019)===================

关于第三个问题,当时写的时候就感受很恶心,做者已经出了一个新版本(1.0.0-rc.1),config里面多了一项配置
'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 60)
当多个并发请求使用相同的JWT进行时,因为 access_token 的刷新 ,其中一些可能会失败,以秒为单位设置请求时间以防止并发的请求失败。
你们能够享用这个新版,能够很好解决这个问题。中间件更新了部分:

try {
    // 刷新token,超时刷新将会去catch
    $refresh = JWTAuth::parseToken()->refresh();
    $user = $auth->authenticate($refresh);
    // 生成新token(上面的$refresh也是合法的刷新token,这里若是直接用,2次刷新以后再没法继续得到,亲测)
    $newToken = JWTAuth::fromUser($user);
    // 给当前的请求设置性的token,以备在本次请求中须要调用用户信息
    $request->headers->set('Authorization','Bearer '.$newToken);
}
相关文章
相关标签/搜索