Laravel JWT 令牌在身份验证 JWT 方法中刷新后无效

Posted

技术标签:

【中文标题】Laravel JWT 令牌在身份验证 JWT 方法中刷新后无效【英文标题】:Laravel JWT tokens are Invalid after refresh them in a authentication JWT approach 【发布时间】:2015-06-24 13:03:54 【问题描述】:

编辑:

阅读关于该错误的讨论:https://github.com/tymondesigns/jwt-auth/issues/83

我的原始问题:

我正在使用jwt-auth 我的受保护资源来实现,这些资源需要经过身份验证的用户并使用以下代码:

Route::group(['middleware' => ['before' => 'jwt.auth', 'after' => 'jwt.refresh']], function() 
    // Protected routes
);

当用户在 API 上“登录”时,会创建一个授权令牌,并将响应授权标头发送到调用该资源的客户端应用程序。因此,客户端应用程序在拦截任何响应的标头上的授权令牌时,使用此令牌值设置变量/会话/任何内容,以便在下一次请求时再次发送到 API。

“登录”后对受保护资源的第一次请求工作正常,但下一个客户端应用程序向 API 发出刷新令牌的请求,会出现以下错误(API 以 json 格式装载所有响应):


    "error": "token_invalid"

刷新的令牌会发生什么?我的刷新令牌实现(设置为中间件后)是错误的?还是不需要手动刷新客户端应用请求附带的所有授权令牌?

更新:

我按照建议 here 更新 jwt-auth RefreshToken 中间件,但 token_invalid 仍然存在。

错误:

我想我发现了发生了什么。请注意,在刷新方法中,旧令牌被添加到黑名单缓存中启用:

// Tymon\JWTAuth\JWTManager
public function refresh(Token $token)

    $payload = $this->decode($token);

    if ($this->blacklistEnabled) 
        // invalidate old token
        $this->blacklist->add($payload);
    

    // return the new token
    return $this->encode(
        $this->payloadFactory->setRefreshFlow()->make([
            'sub' => $payload['sub'],
            'iat' => $payload['iat']
        ])
    );

请注意,除了黑名单方法,关键是旧令牌有效负载中的 jti 参数:

// Tymon\JWTAuth\Blacklist
public function add(Payload $payload)

    $exp = Utils::timestamp($payload['exp']);

    // there is no need to add the token to the blacklist
    // if the token has already expired
    if ($exp->isPast()) 
        return false;
    

    // add a minute to abate potential overlap
    $minutes = $exp->diffInMinutes(Utils::now()->subMinute());

    $this->storage->add($payload['jti'], [], $minutes);

    return true;

因此,当调用has on blacklist方法时,旧token jti参数与new相同,因此新token在黑名单中:

// Tymon\JWTAuth\Blacklist
public function has(Payload $payload)

    return $this->storage->has($payload['jti']);

如果您不需要黑名单功能,只需在 jwt.php 配置文件中设置为 false。但我不能说它是否暴露了一些安全漏洞。

阅读关于该错误的讨论:https://github.com/tymondesigns/jwt-auth/issues/83

【问题讨论】:

您的问题在新版本 0.5.3 中解决了吗?正如作者所说,它已在最新版本中解决。但我仍然遇到和你一样的问题。 @davidcoder 当我发现这个问题时,我为 jwt-auth 编写了一个包装器来解决它,因为我需要一个快速的解决方案。基本上我覆盖了我的 App\Http\Middleware 包中的 RefreshToken 中间件。但我会测试新版本以验证发生了什么。 我可以看出问题仍然存在。非常感谢你的洞察力。禁用黑名单工作 我希望每个请求都应该有令牌,即使来自不同的控制器,我该怎么做? 【参考方案1】:

当我遇到这个问题时,我发现让我的项目正常工作的解决方案是在每个新请求上使用旧令牌中的数据生成一个新令牌。

适用于我的解决方案很糟糕,很丑陋,并且如果您有很多异步请求并且您的 API(或业务核心)服务器很慢,则会产生更多问题。

目前正在工作,但我会进一步调查这个问题,因为在 0.5.3 版本之后问题仍然存在。

例如:

请求 1 (GET /login):

Some guest data on token

请求 2(POST /登录响应):

User data merged with guest data on old token generating a new token

程序代码示例(你可以做得更好=)),你可以在routes.php上运行这个,我说这很难看哈哈:

// ----------------------------------------------------------------
// AUTH TOKEN WORK
// ----------------------------------------------------------------
$authToken = null;
$getAuthToken = function() use ($authToken, $Response) 
    if($authToken === null) 
         $authToken = JWTAuth::parseToken();
    
    return $authToken;
;

$getLoggedUser = function() use ($getAuthToken) 
    return $getAuthToken()->authenticate();
;

$getAuthPayload = function() use ($getAuthToken) 
    try 
        return $getAuthToken()->getPayload();
     catch (Exception $e) 
        return [];
    
;

$mountAuthPayload = function($customPayload) use ($getLoggedUser, $getAuthPayload) 
    $currentPayload = [];
    try 
        $currentAuthPayload = $getAuthPayload();
        if(count($currentAuthPayload)) 
            $currentPayload = $currentAuthPayload->toArray();
        
        try 
            if($user = $getLoggedUser()) 
                $currentPayload['user'] = $user;
            
            $currentPayload['isGuest'] = false;
         catch (Exception $e) 
            // is guest
        
     catch(Exception $e) 
        // Impossible to parse token
    

    foreach ($customPayload as $key => $value) 
        $currentPayload[$key] = $value;
    

    return $currentPayload;
;

// ----------------------------------------------------------------
// AUTH TOKEN PAYLOAD
// ----------------------------------------------------------------
try 
    $getLoggedUser();
    $payload = ['isGuest' => false];
 catch (Exception $e) 
    $payload = ['isGuest' => true];


try 
    $payload = $mountAuthPayload($payload);
 catch (Exception $e) 
    // Make nothing cause token is invalid, expired, etc., or not exists.
    // Like a guest session. Create a token without user data.

一些路线(保存用户移动设备的简单示例):

Route::group(['middleware' => ['before' => 'jwt.auth', 'after' => 'jwt.refresh']], function () use ($getLoggedUser, $mountAuthPayload) 
    Route::post('/session/device', function () use ($Response, $getLoggedUser, $mountAuthPayload) 
        $Response = new \Illuminate\Http\Response();
        $user = $getLoggedUser();

        // code to save on database the user device from current "session"...

        $payload = app('tymon.jwt.payload.factory')->make($mountAuthPayload(['device' => $user->device->last()->toArray()]));
        $token = JWTAuth::encode($payload);
        $Response->header('Authorization', 'Bearer ' . $token);

        $responseContent = ['setted' => 'true'];

        $Response->setContent($responseContent);
        return $Response;
    );
);

【讨论】:

我希望每个请求都应该有令牌,即使来自不同的控制器,我该怎么做?

以上是关于Laravel JWT 令牌在身份验证 JWT 方法中刷新后无效的主要内容,如果未能解决你的问题,请参考以下文章

JWT 从 Laravel 中的令牌中检索经过身份验证的用户

Laravel 通过生成的令牌进行身份验证,无需护照和 jwt

具有相同令牌的多个 Laravel-API 的 JWT 身份验证

Laravel Tymon\JWT Auth:在身份验证之前检查未完成的有效令牌?

没有数据库的 Laravel 中的 JWT 身份验证

Laravel 未检测到标头和 JWT 包中发送的身份验证令牌