Почему время сеанса с jwt-auth составляет 1 час с момента входа в систему? - PullRequest
0 голосов
/ 27 мая 2020

В приложении laravel 7 backend rest api я использую jwt-auth, и у меня проблема, что при входе в систему я могу работать во фронтенд-части, но через 1 час у меня возникла ошибка TOKEN_EXPIRED.

1) Я попробовал установить время сеанса больше, но не удалось. На стадии разработки мне нужно время сеанса более 1 часа. Двигаясь в прямом эфире, я установлю время сеанса 30 минут.

2) Я ожидал, что сеанс больше на 1 час от последнего запроса зарегистрированного пользователя к бэкэнду, но не от входа в систему

У меня есть refre sh метод реализован ниже, но выглядит как refre sh не работает ...

app / Http / Controllers / API / AuthController. php:

<?php

namespace App\Http\Controllers\API;

use App\library\CheckValueType;
use App\Settings;
use Auth;
use Config;
use DB;
use Validator;
use Carbon\Carbon;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Support\Str;
use App\UserGroup;
use App\Http\Resources\UserCollection;
use Avatar;
use Storage;

class AuthController extends Controller
{

    /**
     * Create a new AuthController instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('jwt.auth', ['except' => ['login', 'register', 'activate']]);
    }

    public function login(Request $request)
    {
        $credentials = $request->only('email', 'password');

        if ($token = $this->guard('api')->attempt($credentials)) {
            $loggedUser = $this->guard('api')->user();

            if ($loggedUser->status != 'A') {
                return response()->json(['error' => 'Unauthorized'], HTTP_RESPONSE_NOT_UNAUTHORIZED);
            }
            $loggedUser->last_logged = Carbon::now(config('app.timezone'));
            $loggedUser->save();

            $userGroupsCount = UserGroup
                ::getByUserId($loggedUser->id)
                ->count();
            if ($userGroupsCount == 0) {
                return response()->json(['error' => 'Unauthorized'], HTTP_RESPONSE_NOT_UNAUTHORIZED);
            }

            return $this->respondWithToken($token);
        }

        return response()->json(['error' => 'Unauthorized'], HTTP_RESPONSE_NOT_UNAUTHORIZED);
    }


    public function me()
    {
        return response()->json($this->guard('api')->user());
    }

    public function logout()
    {
        $this->guard('api')->logout();
        return response()->json(['message' => 'Successfully logged out']);
    }

    public function refresh()
    {
        return $this->respondWithToken($this->guard()->refresh());
    }

    protected function respondWithToken($token)
    {
        $loggedUser = $this->guard()->user();

        $user_avatar_path = 'public/' . User::getUserAvatarPath($loggedUser->id, $loggedUser->avatar);
        $filenameData     = User::setUserAvatarProps($loggedUser->id, $loggedUser->avatar, true);
        $usersGroups = User::getUserGroupByUserId($loggedUser->id, false);

        return response()->json([
            'access_token'     => $token,
            'user'             => $loggedUser,
            'token_type'       => 'bearer',
            'user_avatar_path' => $user_avatar_path,
            'filenameData'     => $filenameData,
            'usersGroups'      => $usersGroups,
            'expires_in'       => $this->guard('api')->factory()->getTTL() * 999360 // I SET VERY BIG VALUE
        ]);
    }

    public function guard()
    {
        return \Auth::Guard('api');
    }

В /config/auth.php:

<?php

return [


    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'api' => [
            'driver' => 'jwt',
            'provider' => 'users',
            'hash' => false,
        ],
    ],

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\User::class,
        ],
    ],

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 99360, // I SET BIG VALUE
            'throttle' => 98860,
        ],
    ],


    'password_timeout' => 10800,  // I SET BIG VALUE

В приложении / Исключениях / Обработчик. php У меня есть:

public function render($request, Throwable $exception)
{

    if ($exception instanceof UnauthorizedHttpException) {
        if ($exception->getPrevious() instanceof TokenExpiredException) {
            \Log::info( '-2 UnauthorizedHttpException TokenExpiredException::' ); // I SEE THIS ERROR LOGGED
            return response()->json(['error' => 'TOKEN_EXPIRED'], $exception->getStatusCode());

У меня:

"laravel/framework": "^7.0",
"tymon/jwt-auth": "^1.0.0",

Что не так в моей конфигурации?

ИЗМЕНИТЬ:

Я добавил файл app / Http / Middleware / JwtMiddleware. php с содержимым и 1 строкой ошибки ведение журнала:

<?php

namespace App\Http\Middleware;

use Closure;
use Tymon\JWTAuth\Exceptions\JWTException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;

class JwtMiddleware extends BaseMiddleware
{
    public function handle($request, Closure $next)
    {
        try {
            if (! $user = $this->auth->parseToken()->authenticate()) {
                return response()->json(['success' => false, 'error' => __('Invalid User.')]);
            }
        } catch (TokenExpiredException $e) {
            try {
                $refreshed = $this->auth->refresh($this->auth->getToken());
                $user = $this->auth->setToken($refreshed)->toUser();
                header('Authorization: Bearer ' . $refreshed);
            } catch (JWTException $e) {
                return response()->json(['success' => false, 'error' => __('Could not generate refresh token')]);
            }
        } catch (JWTException $e) {
            \Log::info( '-1 JwtMiddleware$e->getMessage() ::' . print_r(  $e->getMessage(), true  ) );

            return response()->json(['success' => false, 'error' => __('Invalid request')]);
        }

        return  $next($request);
    }
}

и я добавил в файл app / Http / Kernel. php:

   ...
    protected $middleware = [
        \Fruitcake\Cors\HandleCors::class,
        \App\Http\Middleware\TrustProxies::class,
        \App\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \App\Http\Middleware\JwtMiddleware::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class
    ];
   ...

and running the page site is broken and I see lines in log :
[2020-05-27 17:08:33] local.INFO: -1 JwtMiddleware$e->getMessage() ::The token could not be parsed from the request  
[2020-05-27 17:08:33] local.INFO: -1 JwtMiddleware$e->getMessage() ::The token could not be parsed from the request  
[2020-05-27 17:08:33] local.INFO: -1 JwtMiddleware$e->getMessage() ::The token could not be parsed from the request  

Может быть какой-то конфликт с кодом app / Http / Controllers / API /AuthController.php? Как это исправить?

ИЗМЕНЕНО # 2: Спасибо! Я исправил ошибку и в .env добавил строки:

JWT_TTL=20 # 20 minutes
JWT_REFRESH_TTL=20160 # 2 weeks

и очистив кеш, я снова вошел в систему, и в результате во время работы в приложении я не выходил из системы через 20 минут, но когда я оставил приложение открытым не работая с ним в течение примерно 30 минут, я мог бы продолжить работу без выхода из системы, как я и ожидал. Есть ли другие варианты?

ИЗМЕНЕНО # 3: В клиентской части моего файла vuejs я предпочитаю перехватчики запросов в src / App. vue:

created() {
    let self = this
    this.$http.interceptors.response.use(undefined, function (error) {

        return new Promise(function (/*resolve, reject*/) {
            if (typeof error.response.status !== 'undefined' && error.response.status === 401) {
                self.$store.dispatch('logout')  // DEBUGGING
                self.showPopupMessage('Access', 'Not authorized !', 'warn')
                let splitted0 = self.getSplitted(error.response.config.url, '/login', 0)

                if (splitted0 == '') { // not move from login page
                    self.$router.push('/login')   // DEBUGGING
                }
            }

            if (typeof error.response.status !== 'undefined') {
                if (error.response.status === 401) {
                    self.$store.dispatch('logout') // DEBUGGING
                    self.showPopupMessage('Access', 'Not authorized !', 'warn')
                    self.$router.push('/login')  // DEBUGGING
                }
            }

            throw error
        })
    })


}, //  created() {

Я ловлю в нем ошибку 401 и задаюсь вопросом, можно ли перехватить запрос с сервера

header('Authorization: Bearer ' . $refreshed);

и записать новое значение access_token из $ refreshed? Но как его поймать? Но какой-то специальный код запроса на возврат?

Спасибо!

Ответы [ 5 ]

1 голос
/ 14 июня 2020

У вас должен быть файл config/jwt.php. Если у вас его еще нет, запустите:

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

В этом файле вы можете увидеть несколько свойств, которые относятся к .env переменным, а именно:

  • 'ttl' (env = 'JWT_TTL')
  • 'refresh_ttl' (env = 'JWT_REFRESH_TTL')

Кроме того, вы можете добавить 'refresh' в этот jwt.auth список исключений промежуточного программного обеспечения потому что иначе он не будет работать, когда вы вызовете функцию refresh() в контроллере.

ОДНАКО, в этом пакете есть несколько важных моментов:

TTL аутентификации связаны между собой

Я считаю, что вы можете в значительной степени игнорировать свойство ttl, поскольку именно refresh_ttl фактически определяет, как долго ваши JWT действительны, основываясь на этой аналогии:

Слесарь (провайдер аутентификации) дает вам Красный ключ (JWT). Этот ключ позволяет вам открыть Красную Дверь (используйте свой бэкэнд). Клавиша автоматически щелкает пополам через 5 минут (TTL = 5 минут). Однако вы можете go вернуться к слесарю со своим сломанным ключом, и он заменит его в течение 2 недель (Refre sh TTL = 2w).

Вы можете просто позвонить auth/refresh с истекшим сроком действия JWT, и он будет обновлять sh его, пока не истечет срок действия Refre sh -TTL.

См. Также [эту проблему на github] [1], где также объясняется, почему вам добавьте функцию refresh, чтобы перед ней не было промежуточного программного обеспечения jwt.auth.

Вы не сохраняете никаких вызовов БД при использовании JWT, как предусмотрено этим пакетом

Моя первоначальная мысль была что это может сэкономить мне путь к базе данных и обратно, потому что пользовательский объект будет подписан и готов внутри JWT.

Я использовал этот пакет в нескольких проектах и ​​пришел к выводу, что это что-то вроде прославленного сессионного кулинара ie. В tymon/jwt утверждение sub просто равно 1 (или в зависимости от того, какой ID пользователя указан здесь).

Это означает, что вам нужно либо ...

  • . .. (по умолчанию) пусть tymon/jwt выполняет работу, которая просто извлекает пользователя из базы данных и применяет его к вашему провайдеру аутентификации, поэтому Auth::user() et c будет работать.

OR

  • ... добавьте заявку самостоятельно с полным объектом пользователя и измените этот пакет в разных местах. Это может сэкономить вам вызовы БД, поскольку этот объект будет частью вашего JWT и подписан в нем. Это приводит к тому, что при каждом запросе отправляется больше байтов JWT, но вы можете в основном принять данный объект как должное (т.е. принять его по мере его получения) и использовать его. Затем вы также можете добавить разрешения / роли таким образом, чтобы их также не нужно было извлекать из базы данных. Эта настройка ДЕЙСТВИТЕЛЬНО означает, что при изменении любых разрешений в вашей базе данных вам придется ждать обновления нового JWT, чтобы увидеть эти изменения.
1 голос
/ 27 мая 2020

попробуйте это в своем логине

if ($token = $this->guard('api')->attempt($credentials,['exp' => Carbon\Carbon::now()->addHours(2)->timestamp])) {

}

Или вы можете заменить ttl в config/jwt

Сообщите мне, поможет ли это!

1 голос
/ 11 июня 2020

go в ---> config / jwt. php Это можно сделать

'ttl' => env('JWT_TTL', 1440)

ИЛИ в контроллере

$token = JWTAuth::attempt($credentials, ['exp' => Carbon\Carbon::now()->addDays(7)->timestamp]);

ИЛИ

config()->set('jwt.ttl', 60*60*7);

ИЛИ

Config::set('jwt.ttl', 60*60*7);
1 голос
/ 27 мая 2020
  • Вам необходимо добавить промежуточное ПО и проверить, не истек ли токен.
  • Если истек, проверьте, какое время генерации, чтобы проверить, следует ли вам генерировать новый токен или нет. Думайте об этом как о времени истечения срока действия токена refre sh. вы можете пропустить этот шаг, если хотите сгенерировать токен refre sh без каких-либо проверок времени.
  • Теперь, если все в порядке, сгенерируйте новый токен и отправьте его в заголовке / теле, как вы sh и сохраните это в своем веб-приложении.
<?php

namespace App\Http\Middleware;

use Closure;
use Tymon\JWTAuth\Exceptions\JWTException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;

class JwtMiddleware extends BaseMiddleware
{
    /**
   * Handle an incoming request.
   *
   * @param  \Illuminate\Http\Request  $request
   * @param  \Closure  $next
   * @return mixed
   */
    public function handle($request, Closure $next)
    {
        try {
            if (! $user = $this->auth->parseToken()->authenticate()) {
                return response()->json(['success' => false, 'error' => __('Invalid User.')]);
            }
        } catch (TokenExpiredException $e) {
            try {
                $refreshed = $this->auth->refresh($this->auth->getToken());
                $user = $this->auth->setToken($refreshed)->toUser();
                header('Authorization: Bearer ' . $refreshed);
            } catch (JWTException $e) {
                return response()->json(['success' => false, 'error' => __('Could not generate refresh token')]);
            }
        } catch (JWTException $e) {
            return response()->json(['success' => false, 'error' => __('Invalid request')]);
        }

        return  $next($request);
    }
}

ОБНОВЛЕНИЯ:

Это метод, которым я следовал в своем недавнем приложении Ionic / Angular. И работает нормально. Есть способы, которые я делал раньше, но это сложно объяснить, дайте мне знать, если у вас возникнут какие-либо проблемы.

  • Вы генерируете токен и refresh_token, когда вы успешно входите в систему и отправляете оба токены в ваше внешнее приложение и сохраните их там в локальном хранилище.
  • Для всех защищенных API теперь вам нужно отправить токен и refresh_token.
  • На стороне API проверьте токен и, если срок его действия истек, проверьте, действителен ли refresh_token или просрочен. По истечении срока вы выходите из системы.
  • Если refresh_token действителен, сгенерируйте новый токен и refresh_token, отправьте его во внешний интерфейс и сохраните там.
  • У этого метода есть одна проблема: сеанс входа в систему должен быть активен, пока пользователь использует приложение. Итак, предположим, что ваш JWT_REFRESH_TTL составляет 1 день, и пользователь использует незадолго до 24 часов, он будет работать до того, как пройдут 24 часа, но он не будет работать сразу после 1 минуты, поскольку оба токена и refresh_token истекут.
  • Чтобы преодолеть это, вы можете генерировать новый refresh_token с каждым запросом API и обновлять его в своем интерфейсе.

Как правило, JWT_TTL должен быть очень коротким, например, 5 минут или около того, а JWT_REFRESH_TTL должно быть время активности сеанса.

0 голосов
/ 15 июня 2020

Как вам предлагали другие люди, go на ваш config/jwt.php и измените лимит срока действия токена:

// ...
'ttl' => env('JWT_TTL', 3600)

Важно: После изменения очистить ваш кеш конфигурации

php artisan config:clear

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

Дополнительное примечание

В качестве альтернативы самостоятельной реализации JWT вы можете использовать Sanctum, новый собственный пакет Laravel. Проверьте режим SPA в документации Sanctum .

...