При использовании 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 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
- Полное удаление Паспорта, удаление всех файлов, ключей, миграций и записей БД с последующим выполнением миграции: обновление и переустановка Паспорта, а также создание дополнительного клиента персонального доступа (даже если он создается во время установки паспорта).
Я не уверен, где еще искать / что еще попробовать на этом этапе, поэтому любая помощь или руководство будут очень благодарны!
Ответы
В конце концов я нашел решение. Проблема является многослойной, отчасти связанной с устаревшей документацией 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();
}
Это создаст нового клиента, установит секрет атрибута на значение в переменной и обновит фиктивный секрет базы данных, чтобы он содержал то же значение. Надеюсь, это поможет любому, у кого такая же проблема.