Локальные жетоны JWT недействительны после обновления их в подходе к проверке подлинности 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 как предложение здесь , но 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 из старой полезной нагрузки token:

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

Таким образом, когда вызывается метод blacklist, старый токен jti param тот же, что и новый, поэтому новый токен находится в черном списке:

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

Если вам не нужны функции черного списка, просто установите значение false в файле конфигурации jwt.php. Но я не могу сказать, подвергает ли она некоторую уязвимость системы безопасности.

Прочитайте дискуссию об ошибке: https://github.com/tymondesigns/jwt-auth/issues/83

Related of "Локальные жетоны JWT недействительны после обновления их в подходе к проверке подлинности JWT"

Когда я получаю эту проблему, решение, которое я нашел для моего проекта, заключалось в создании нового токена с данными из старого токена для каждого нового запроса.

Мое решение, которое работает для меня, плохое, уродливое и может генерировать больше проблем, если у вас много асинхронных запросов, а ваш сервер 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 

Пример процедурного кода (вы можете сделать лучше =)), вы можете запустить это на route.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; }); });