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);
    }
那么后端该怎么处理,怎么控制在什么情况下刷新,刷新后怎么反馈给前端
本帖已被设为精华帖!
本帖由系统于 5年前 自动加精
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
最佳答案

找到方案了,建一个验证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

6年前 评论
passport4jd 5年前
ginkgo6 5年前
jehaz007 (作者) (楼主) 5年前
laraverer 5年前
jehaz007 (作者) (楼主) 5年前
sy_dante 4年前
讨论数量: 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

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

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

6年前 评论

找到方案了,建一个验证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

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

file

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

6年前 评论

@gaoxiang

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

6年前 评论

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

6年前 评论

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

6年前 评论

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

6年前 评论

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

6年前 评论
vtrtbbs

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

版本Laravel5.4

6年前 评论

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

6年前 评论

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

6年前 评论

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

6年前 评论

@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))"
...
6年前 评论
qqww11 5年前
与其感慨路难行 4年前
osang 4年前

@Sher

刷新 / 删除 token

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

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

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

5年前 评论

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

5年前 评论

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

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

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

3年前 评论

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