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

Aug 19 2020

Я разрабатываю настраиваемую схему аутентификации (на основе открытых ключей) вместе с 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 passed in is null (which explains why the $client-> id не может быть найден, хотя я понятия не имею, почему $ client на данный момент равен нулю).

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
  • Полное удаление Паспорта, удаление всех файлов, ключей, миграций и записей БД с последующим выполнением миграции: обновление и переустановка Паспорта, а также создание дополнительного клиента персонального доступа (даже если он создается во время установки паспорта).

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

Ответы

grantley Aug 21 2020 at 22:41

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

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

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

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

Решение этой проблемы состоит в том, чтобы создать файл .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();
}

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