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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29703260/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 11:22:27  来源:igfitidea点击:

Laravel JWT tokens are Invalid after refresh them in a authentication JWT approach

phpangularjsauthenticationlaraveljwt

提问by Maykonn

EDIT:

编辑:

Read the discussion about the bug at: https://github.com/tymondesigns/jwt-auth/issues/83

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

MY ORIGINAL QUESTION:

我的原始问题:

I'm implement with jwt-authmy protected resources that require an authenticated user with bellow code:

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

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

When user 'sign in' on API an Authorization token is created, and sent on response Authorization header to client application that call the resource. So, client applications when intercept a Authorization token on header of any response, set a variable/session/whatever with this token value, to send again to API on next request.

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

The first request for a protected resource after 'login' works fine, but the next client application request to API with a refreshed token, gives the following error (API mount all responses in json format):

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

{
    "error": "token_invalid"
}

What can be happen with refreshed tokens? My refresh token implementation (set as a after middleware) is wrong? Or isn't necessary to manually refresh all Authorization token that come with client apps requests?

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

UPDATE:

更新:

I update the jwt-auth RefreshToken middleware as propose here, but the token_invalidpersist.

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

BUG:

漏洞:

I guess that I found what happens. Note that in the refresh method, old token is added to blacklist cache case enabled:

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

// 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']
        ])
    );
}

And note that in add to blacklist method the key is the jti param from old token payload:

并注意,添加到黑名单方法的关键是来自旧令牌有效负载的 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;
}

Thus, when has on blacklist method is called, the old token jti param is the same that the new, so the new token is in blacklist:

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

// Tymon\JWTAuth\Blacklist
public function has(Payload $payload)
{
    return $this->storage->has($payload['jti']);
}

If you don't need the blacklist functionality just set to false on jwt.php configuration file. But I can't say if it expose to some security vulnerability.

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

Read the discussion about the bug at: https://github.com/tymondesigns/jwt-auth/issues/83

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

采纳答案by Maykonn

When I get this issue, the solution that I found to get my project working was to generate a new token with data from older token on each new request.

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

My solution, that works for me, is bad, ugly, and can generate more issues if you have many async requests and your API(or business core) server is slow.

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

For now is working, but I will investigate more this issue, cause after 0.5.3 version the issue continues.

目前正在工作,但我会调查更多这个问题,导致 0.5.3 版本后问题继续存在。

E.g:

例如:

Request 1 (GET /login):

请求 1(获取/登录):

Some guest data on token

Request 2 (POST /login response):

请求 2(POST /login 响应):

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

Procedural code example(you can do better =) ), you can run this on routes.php out of routes, I say that is ugly haha:

程序代码示例(你可以做得更好=)),你可以在routes.php out routes上运行这个,我说这是丑陋的哈哈:

// ----------------------------------------------------------------
// 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.
}

Some route(simple example to save user mobile device):

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

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;
    });
});