在使用身份验证JWT方法刷新它们之后,Laravel JWT令牌无效

时间:2015-04-17 15:20:33

标签: php angularjs authentication laravel jwt

修改

https://github.com/tymondesigns/jwt-auth/issues/83

阅读有关错误的讨论

我的原始问题:

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

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

当用户登录'在API上创建授权令牌,并在响应授权头上发送到调用资源的客户端应用程序。因此,客户端应用程序在拦截任何响应的标头上的授权令牌时,使用此令牌值设置变量/ session / whatever,以便在下次请求时再次发送给API。

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

{
    "error": "token_invalid"
}

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

更新

我将jwt-auth RefreshToken中间件更新为here,但token_invalid仍然存在。

BUG:

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

// 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 param:

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

因此,当调用黑名单方法时,旧标记jti param与新标记相同,因此新标记位于黑名单中:

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

阅读有关错误的讨论

1 个答案:

答案 0 :(得分:4)

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

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

目前正在运作,但我会调查更多这个问题,因为在0.5.3版本之后问题仍在继续。

E.g:

请求1(GET /登录):

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