jwt 刷新 token 具体方案,见答案

看了还是不明白token到底该怎么刷新,前端发一个请求过来,我在RedirectIfAuthenticated中间件里面监听是否过期,

public function handle($request, Closure $next, $guard = null)
    {
        Event::listen('tymon.jwt.expired', function () {
            //这里该怎么写,调用刷新token方法,新的token又怎么反馈给前端,同时又能继续执行之前的请求呢
            //return $this->errorResponse(401,'Token 已经过期, 请重新登录');
        });
        return $next($request);
    }
那么后端该怎么处理,怎么控制在什么情况下刷新,刷新后怎么反馈给前端
本帖已被设为精华帖!
本帖由系统于 4年前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
最佳答案

找到方案了,建一个验证token的中间件,替代 jwt.auth 中间件,验证如果 token 过期则重新生成,并在 header 头中返回,客户端检查 header 有新的 Authorization 就替换原来的。 :star2: :sunny:

<?php

namespace App\Http\Middleware;

use Auth;
use Closure;
use Tymon\JWTAuth\JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

class RefreshToken extends BaseMiddleware
{
    function handle($request, Closure $next)
    {
        // 检查此次请求中是否带有 token,如果没有则抛出异常。 
        $this->checkForToken($request);

       // 使用 try 包裹,以捕捉 token 过期所抛出的 TokenExpiredException  异常
        try {
            // 检测用户的登录状态,如果正常则通过
            if ($this->auth->parseToken()->authenticate()) {
                return $next($request);
            }
            throw new UnauthorizedHttpException('jwt-auth', '未登录');
        } catch (TokenExpiredException $exception) {
          // 此处捕获到了 token 过期所抛出的 TokenExpiredException 异常,我们在这里需要做的是刷新该用户的 token 并将它添加到响应头中
            try {
                // 刷新用户的 token
                $token = $this->auth->refresh();
               // 使用一次性登录以保证此次请求的成功
                Auth::guard('api')->onceUsingId($this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()['sub']);
            } catch (JWTException $exception) {
               // 如果捕获到此异常,即代表 refresh 也过期了,用户无法刷新令牌,需要重新登录。
                throw new UnauthorizedHttpException('jwt-auth', $exception->getMessage());
            }
        }
        // 在响应头中返回新的 token
        return $this->setAuthenticationHeader($next($request), $token);
    }
}
  • `App/Http/Kernel.php 文件 $routeMiddleware 中添加中间件 'token.canrefresh' => \App\Http\Middleware\RefreshToken::class,
  • 路由中配置 'middleware' => ['token.canrefresh'] 中间件即可

我们用过期的 token 访问,发现响应头中已经生成了新的 token :file

5年前 评论
passport4jd 4年前
ginkgo6 4年前
jehaz007 (作者) (楼主) 4年前
laraverer 4年前
jehaz007 (作者) (楼主) 4年前
sy_dante 3年前
讨论数量: 32

找到方案了,建一个验证token的中间件,替代 jwt.auth 中间件,验证如果 token 过期则重新生成,并在 header 头中返回,客户端检查 header 有新的 Authorization 就替换原来的。 :star2: :sunny:

<?php

namespace App\Http\Middleware;

use Auth;
use Closure;
use Tymon\JWTAuth\JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

class RefreshToken extends BaseMiddleware
{
    function handle($request, Closure $next)
    {
        // 检查此次请求中是否带有 token,如果没有则抛出异常。 
        $this->checkForToken($request);

       // 使用 try 包裹,以捕捉 token 过期所抛出的 TokenExpiredException  异常
        try {
            // 检测用户的登录状态,如果正常则通过
            if ($this->auth->parseToken()->authenticate()) {
                return $next($request);
            }
            throw new UnauthorizedHttpException('jwt-auth', '未登录');
        } catch (TokenExpiredException $exception) {
          // 此处捕获到了 token 过期所抛出的 TokenExpiredException 异常,我们在这里需要做的是刷新该用户的 token 并将它添加到响应头中
            try {
                // 刷新用户的 token
                $token = $this->auth->refresh();
               // 使用一次性登录以保证此次请求的成功
                Auth::guard('api')->onceUsingId($this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()['sub']);
            } catch (JWTException $exception) {
               // 如果捕获到此异常,即代表 refresh 也过期了,用户无法刷新令牌,需要重新登录。
                throw new UnauthorizedHttpException('jwt-auth', $exception->getMessage());
            }
        }
        // 在响应头中返回新的 token
        return $this->setAuthenticationHeader($next($request), $token);
    }
}
  • `App/Http/Kernel.php 文件 $routeMiddleware 中添加中间件 'token.canrefresh' => \App\Http\Middleware\RefreshToken::class,
  • 路由中配置 'middleware' => ['token.canrefresh'] 中间件即可

我们用过期的 token 访问,发现响应头中已经生成了新的 token :file

5年前 评论
passport4jd 4年前
ginkgo6 4年前
jehaz007 (作者) (楼主) 4年前
laraverer 4年前
jehaz007 (作者) (楼主) 4年前
sy_dante 3年前

刷新 token 需要实现 jwt 的 refresh 方法

5年前 评论

找到方案了,建一个验证token的中间件,替代 jwt.auth 中间件,验证如果 token 过期则重新生成,并在 header 头中返回,客户端检查 header 有新的 Authorization 就替换原来的。 :star2: :sunny:

<?php

namespace App\Http\Middleware;

use Auth;
use Closure;
use Tymon\JWTAuth\JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

class RefreshToken extends BaseMiddleware
{
    function handle($request, Closure $next)
    {
        // 检查此次请求中是否带有 token,如果没有则抛出异常。 
        $this->checkForToken($request);

       // 使用 try 包裹,以捕捉 token 过期所抛出的 TokenExpiredException  异常
        try {
            // 检测用户的登录状态,如果正常则通过
            if ($this->auth->parseToken()->authenticate()) {
                return $next($request);
            }
            throw new UnauthorizedHttpException('jwt-auth', '未登录');
        } catch (TokenExpiredException $exception) {
          // 此处捕获到了 token 过期所抛出的 TokenExpiredException 异常,我们在这里需要做的是刷新该用户的 token 并将它添加到响应头中
            try {
                // 刷新用户的 token
                $token = $this->auth->refresh();
               // 使用一次性登录以保证此次请求的成功
                Auth::guard('api')->onceUsingId($this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()['sub']);
            } catch (JWTException $exception) {
               // 如果捕获到此异常,即代表 refresh 也过期了,用户无法刷新令牌,需要重新登录。
                throw new UnauthorizedHttpException('jwt-auth', $exception->getMessage());
            }
        }
        // 在响应头中返回新的 token
        return $this->setAuthenticationHeader($next($request), $token);
    }
}
  • `App/Http/Kernel.php 文件 $routeMiddleware 中添加中间件 'token.canrefresh' => \App\Http\Middleware\RefreshToken::class,
  • 路由中配置 'middleware' => ['token.canrefresh'] 中间件即可

我们用过期的 token 访问,发现响应头中已经生成了新的 token :file

5年前 评论
passport4jd 4年前
ginkgo6 4年前
jehaz007 (作者) (楼主) 4年前
laraverer 4年前
jehaz007 (作者) (楼主) 4年前
sy_dante 3年前

file

file
老铁,我按你的写,上面报没有这个方法,,,是少了什么吗?

5年前 评论

@gaoxiang

file
中间件需要继承基础的middleware

5年前 评论

请问什么使用场景需要刷新token?感觉如果过期了的话应该用户退出,就是app不再保持登录,这样用户重新登录获取新的。

5年前 评论

@Sher token是有有效期的,防止别人拿到你的token可以一直登录。所以会设置有效期,过期和失效是两个概念。在失效之前只要你操作,就应该在过期的时候无感刷新token

5年前 评论

@jehaz007 失效前,无感刷新token,那别人拿到token,是不是也会无感被刷新token呢.

5年前 评论

@落伽 所以有个失效时间,你说的这个问题对于所有使用token的都可以这样质疑,在失效前过期可以刷新,但是token是一次性的

5年前 评论
vtrtbbs

Token已经生成, 再提交时Header中加入 Authorization Bearer ................时提交报错
UnauthorizedHttpException
Token not provided

版本Laravel5.4

5年前 评论

@jehaz007 你好,但这种写法刷新token这次请求是获取不到请求消息的。

5年前 评论

@bossaiguo 怎么会,刷新会有刷新token去请求啊

5年前 评论

@落伽 我觉得是这样的,当 token 被窃取 别人用来刷新了新的 token 真实用户这边的 token 就彻底失效了 不能刷到新token,这样真实用户再需要登录 就得重新填写用户名密码。当用户再次登录,窃取者那边的 token 也会失效,只有真实用登录后生成的 token 才是有效的

5年前 评论

@jehaz007 请教一下:我copy你的代码,也注册了中间件,在路由中应该这样使用么?

$api->group(['middleware' => ['refresh.token','api.auth']], function($api) {
...

dingoapi 路由使用:

// 需要 token 验证的接口
$api->group(['middleware' => ['refresh.token','api.auth']], function($api) {
            // 当前登录用户信息
            $api->get('user', 'UsersController@me')
                ->name('api.user.show');
            // 更新个人信息
            $api->post('user/detail', 'UsersController@update')
                ->name('api.user.update');
            // 重置密码
            $api->post('user/password', 'UsersController@reset')
                ->name('api.user.reset');
});

这样的话,会报下面的错,请问是什么原因呢?应该怎么做呢?卡这里了 :sob: :sob:

"message" => "Wrong number of segments"
  "status_code" => 500
  "debug" => array:4 [
    "line" => 42
    "file" => "/home/vagrant/Code/zanhuajob-php/vendor/tymon/jwt-auth/src/Validators/TokenValidator.php"
    "class" => "Tymon\JWTAuth\Exceptions\TokenInvalidException"
    "trace" => array:48 [
      0 => "#0 /home/vagrant/Code/zanhuajob-php/vendor/tymon/jwt-auth/src/Validators/TokenValidator.php(27): Tymon\JWTAuth\Validators\TokenValidator->validateStructure('Bearer')"
      1 => "#1 /home/vagrant/Code/zanhuajob-php/vendor/tymon/jwt-auth/src/Token.php(32): Tymon\JWTAuth\Validators\TokenValidator->check('Bearer')"
      2 => "#2 /home/vagrant/Code/zanhuajob-php/vendor/tymon/jwt-auth/src/JWT.php(304): Tymon\JWTAuth\Token->__construct('Bearer')"
      3 => "#3 /home/vagrant/Code/zanhuajob-php/vendor/tymon/jwt-auth/src/JWT.php(188): Tymon\JWTAuth\JWT->setToken('Bearer')"
      4 => "#4 /home/vagrant/Code/zanhuajob-php/app/Http/Middleware/RefreshToken.php(33): Tymon\JWTAuth\JWT->parseToken()"
      5 => "#5 /home/vagrant/Code/zanhuajob-php/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(149): App\Http\Middleware\RefreshToken->handle(Object(Dingo\Api\Http\Request), Object(Closure))"
      6 => "#6 /home/vagrant/Code/zanhuajob-php/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(53): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Dingo\Api\Http\Request))"
      7 => "#7 /home/vagrant/Code/zanhuajob-php/vendor/dingo/api/src/Http/Middleware/RateLimit.php(70): Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}(Object(Dingo\Api\Http\Request))"
      8 => "#8 /home/vagrant/Code/zanhuajob-php/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(149): Dingo\Api\Http\Middleware\RateLimit->handle(Object(Dingo\Api\Http\Request), Object(Closure))"
      9 => "#9 /home/vagrant/Code/zanhuajob-php/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(53): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Dingo\Api\Http\Request))"
      10 => "#10 /home/vagrant/Code/zanhuajob-php/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(41): Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}(Object(Dingo\Api\Http\Request))"
...
4年前 评论
qqww11 4年前
与其感慨路难行 3年前
osang 3年前

@Sher

刷新 / 删除 token

任何一个永久有效的 token 都是相当危险的,通过任意方式泄露了 token 之后,用户的相关信息都有可能被利用。所以为了安全考虑,任何一种令牌的机制,都会有过期时间,过期时间一般也不会太长。那么 token 过期以后,难道要用户重新登录吗?像 OAuth 2.0 有 refresh_token 可以用来刷新一个过期的 access_token,jwt-auth 同样也为我们提供了刷新的机制,只要在可刷新的时间范围内,即使 token 过期了,依然可以调用接口,换取一个新的 token。这对于 APP 长期保持用户登录状态是十分重要的。

4年前 评论
wangxi
$this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()['sub']

请问这段代码我该怎么理解呢。

4年前 评论

如果前端同时异步请求了两个API,但是在第一次请求内刷新了token并返回给前端,但是第二次请求的token是已经过期的。由于是异步的第二次请求无法获得刷新的token,那第二次请求不就被判定token已过期了?
或者说token的过期时间让前端请求的时候去判断?如果过期了就去刷新 一下token?

4年前 评论

@jehaz007 api如果并发进来请求,自动刷新机制可能会有问题吧,毕竟header中的token被刷新了多次

3年前 评论
jehaz007 (楼主) 3年前
爆炸青山绿水 (作者) 3年前

楼主方案是我见过全网最棒的,给你一个汪汪赞 :+1:

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!