php jwt token刷新方案,解决使用jwt刷新token带来的问题

前后端分离,使用token的方式校验用户信息,我选择了jwt,使用的教程在网上可以找到很多,不做介绍。

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

业务要达到的目标:

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

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

1. 产品要求

半个月内免登陆,这里就要使用到了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调取登录用户信息?

一下子可能没说明问题,简单理解为: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. 关键中间件代码

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;

}

}

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

你可能感兴趣的:(php,jwt,token刷新方案)