Использование Laravel Test 7 и Laravel Passport 9.3 с Personal Access Client дает исключение Попытка получить свойство 'id' не-объекта

Я разрабатываю настраиваемую схему аутентификации (на основе открытых ключей) вместе с API без сохранения состояния и решил, что Passport удовлетворит потребности в запросах после аутентификации.

Предполагая, что аутентификация прошла успешно и пользователь аутентифицирован, они получат токен личного доступа и будут использовать этот токен для всех дальнейших запросов. Проблема, с которой я сталкиваюсь (все еще после долгого поиска по различным форумам и переполнению стека), заключается в том, что при использовании встроенного пакета тестирования Laravel в методе createToken () он генерирует (по общему признанию распространенное) исключение:

ErrorException: попытка получить свойство id не-объекта.

Я могу вручную создать пользователя через Tinker и создать токен через Tinker. Однако у меня возникают проблемы при попытке автоматизировать этот процесс после аутентификации.

Вот соответствующий фрагмент кода после аутентификации:

            Auth::login($user);
            $user = Auth::user();
            $tokenResult = $user->createToken('Personal Access Token');
            $token = $tokenResult->token;

            $token->expires_at = Carbon::now()->addWeeks(1);
            $token->save();            

            return response()->json([
                "access_token" => $tokenResult->accessToken,
                "token_type" => "Bearer",
                "expires_at" => Carbon::parse(
                    $tokenResult->token->expires_at)->toDateTimeString()
            ],
            200);

Я вручную вызвал Auth :: login для пользователя, чтобы убедиться, что пользователь вошел в систему, а Auth :: user () возвращает пользователя (не null). После выполнения третьей строки кода возникает исключение со следующей мини-трассировкой стека (при запросе я могу предоставить полную трассировку стека).

laravel\passport\src\PersonalAccessTokenFactory.php:100
laravel\passport\src\PersonalAccessTokenFactory.php:71
laravel\passport\src\HasApiTokens.php:67
app\Http\Controllers\Auth\LoginController.php:97
laravel\framework\src\Illuminate\Routing\Controller.php:54
laravel\framework\src\Illuminate\Routing\ControllerDispatcher.php:45

От запуска этого через отладку несколько раз - даже если класс вызывается и загружается, и кажется, что Клиент найден через ControllerDispatcher - ›Client :: find (id) и найден в ClientRepository, когда он попадает в PersonalAccessTokenFactory, $ client передано значение null (что объясняет, почему $ client- ›id не может быть найден, хотя я понятия не имею, почему в данный момент $ client имеет значение null).

protected function createRequest($client, $userId, array $scopes)
{
    $secret = Passport::$hashesClientSecrets ? Passport::$personalAccessClientSecret : $client->secret;

    return (new ServerRequest)->withParsedBody([
        'grant_type' => 'personal_access',
        'client_id' => $client->id,
        ...
}

Вещи, которые я сделал / попробовал, руководствуясь документацией и другими сообщениями:

  • Вручную создал пользователя в Tinker и создал токен через Tinker - это работает.
  • Убедитесь, что пользователь вошел в систему, прежде чем пытаться сгенерировать токен.
  • Паспорт: установить (и добавив параметр --force)
  • Гарантированный персональный доступ Клиент генерируется с паспортом: клиент - персональный
  • Убедитесь, что AuthServiceProvider :: boot () содержит ClientID и Client Secret (в .env).
  • migrate: refresh, затем паспорт: install --force
  • Полное удаление Паспорта, удаление всех файлов, ключей, миграций и записей БД с последующим выполнением миграции: обновление и переустановка Паспорта, а также создание дополнительного клиента персонального доступа (даже если он создается во время установки паспорта).

Я не уверен, где еще искать / что еще попробовать на этом этапе, поэтому любая помощь или руководство будут очень благодарны!


person grantley    schedule 18.08.2020    source источник
comment
Измените Auth::user()->id; вместо Auth::user()   -  person Sobir    schedule 18.08.2020
comment
Спасибо за ответ Собир. Он возвращает идентификатор пользователя (в данном случае 1, поскольку это всего лишь пользователь, созданный тестовой фабрикой :)). Вы говорите, что я должен попытаться создать токен, используя идентификатор пользователя вместо объекта пользователя?   -  person grantley    schedule 18.08.2020
comment
Auth::user()->id; возвращает идентификатор текущего авторизованного пользователя. Вы должны создать токен по идентификатору текущего авторизованного пользователя.   -  person Sobir    schedule 18.08.2020
comment
Если вы говорите, что я должен использовать другую функцию для генерации токена, используя только идентификатор (а не объект пользователя), тогда я не уверен, как это сделать. Я просто пытался сделать это, используя документацию, доступную для Laravel Passport, которая показывает: $user = App\User::find(1); // Creating a token without scopes... $token = $user->createToken('Token Name')->accessToken; // Creating a token with scopes... $token = $user->createToken('My Token', ['place-orders'])->accessToken; Все, что я действительно делал с Auth :: user (), это подтверждало, что пользователь действительно вошел в систему.   -  person grantley    schedule 18.08.2020
comment
$user = App\User::find(1); 1 - это user id. Вы должны использовать App\User::find(Auth::user()->id); //return current logged in user id.   -  person Sobir    schedule 18.08.2020
comment
Я понимаю, о чем вы говорите. Я пробовал то, что вы порекомендовали, но, к сожалению, у меня все еще та же проблема. Стоит упомянуть, что, поскольку это находится в методе входа в систему, у меня уже есть объект пользователя (от нахождения его идентификатора) для их аутентификации. Если я чего-то не упускаю, не будет ли User :: find (Auth :: user () - ›id), (после вызова Auth :: login ($ user);) - вернуть тот же объект пользователя, что и объект $ user У меня от вызова User :: find (request () - ›id)? Спасибо за вашу помощь и ответы, я ценю их.   -  person grantley    schedule 18.08.2020
comment
Спасибо за ответ, Курт. Я думал, что это так.   -  person grantley    schedule 19.08.2020


Ответы (3)


В конце концов я нашел решение. Проблема является многоуровневой, отчасти связанной с устаревшей документацией Laravel в отношении тестирования и клиентов персонального доступа Passport.

Первая часть проблемы была связана с использованием трейта RefreshDatabase в моем модульном тесте. Поскольку при этом создается фиктивная база данных с пустыми наборами данных, хотя сами клиенты существуют в реальной базе данных и в файле .env, при запуске теста тест не видит этих клиентов как существующие в фиктивной базе данных. Чтобы решить эту проблему, вы должны создать клиента в функцию настройки перед запуском теста.

public function setUp() : void
{
    parent::setUp();
    $this->createClient(); //Private method->Full code below
}

Это решает проблему наличия нулевого клиента во время тестирования, но начиная с Laravel 7, Laravel добавил требование для Personal Access Clients, что идентификатор и секрет клиента должны храниться в файле .env. При запуске теста тест увидит фактический идентификатор и секрет клиента в .env и не сможет проверить их с клиентом, который был создан и сохранен в фиктивной базе данных, возвращая другое исключение: Ошибка аутентификации клиента.

Решение этой проблемы состоит в том, чтобы создать файл .env.testing в вашем основном каталоге проекта, скопировать в него содержимое файла .env и убедиться, что приведенные ниже ключи существуют со значениями либо для вашего основного созданного клиента Personal Access, либо копируя секрет от клиента, созданного специально для тестирования (я бы посоветовал последнее).

 PASSPORT_PERSONAL_ACCESS_CLIENT_ID=1

 PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET=unhashed-client-secret-value

Затем, используя приведенный ниже код, убедитесь, что значение $ clientSecret совпадает со значением ключа в вашем файле .env.testing.

private function createClient() : void
{
    $clientRepository = new ClientRepository();
    $client = $clientRepository->createPersonalAccessClient(
        null, 'Test Personal Access Client', 'http://localhost'
    );

    DB::table('oauth_personal_access_clients')->insert([
        'client_id'  => $client->id,
        'created_at' => new DateTime,
        'updated_at' => new DateTime,
    ]);

    $clientSecret = 'unhashed-client-secret-value';
    $client->setSecretAttribute($clientSecret);
    $client->save();
}

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

person grantley    schedule 21.08.2020

Другой способ предотвратить копирование / вставку исходного кода - просто вызвать команду artisan в методе установки.

public function setUp() {
        parent::setUp();
        $this->artisan('passport:install');
}

оригинал здесь

person Cristian D. Gonzalez T.    schedule 20.04.2021
comment
На самом деле это было обычно предлагаемое решение, с которым я столкнулся в ходе исследования этой проблемы. Проблема с этим подходом заключается в том, что Passport требует, чтобы PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET в файле .env соответствовал вновь созданному секрету клиента. Поскольку это случайно сгенерированный клиент, эти значения не будут совпадать. Используя описанный выше подход, вы будете вынуждены получить клиента из клиентского репозитория и обновить секрет клиента. - person grantley; 21.04.2021

Просто используйте фасад

 public function setUp() {
    parent::setUp();
    Artisan::call('passport:install');}
person Community    schedule 15.07.2021