Как проверить аутентификацию через API с помощью Laravel Passport? - PullRequest
0 голосов
/ 01 мая 2018

Я пытаюсь проверить аутентификацию с помощью Laravel's Passport, и нет никакого способа ... всегда получал 401 этого клиента недействительным, я оставлю вам то, что я пробовал:

Моя конфигурация phpunit - та, которая приходит из базы с laravel

Тесты / TestCase.php

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication, DatabaseTransactions;

    protected $client, $user, $token;

    public function setUp()
    {
        parent::setUp();

        $clientRepository = new ClientRepository();
        $this->client = $clientRepository->createPersonalAccessClient(
            null, 'Test Personal Access Client', '/'
        );
        DB::table('oauth_personal_access_clients')->insert([
            'client_id' => $this->client->id,
            'created_at' => date('Y-m-d'),
            'updated_at' => date('Y-m-d'),
        ]);
        $this->user = User::create([
            'id' => 1,
            'name' => 'test',
            'lastname' => 'er',
            'email' => 'test@test.test',
            'password' => bcrypt('secret')
        ]);
        $this->token = $this->user->createToken('TestToken', [])->accessToken;
    }
}

Тесты / Feature / AuthTest.php

class AuthTest extends TestCase
{
    use DatabaseMigrations;

    public function testShouldSignIn()
    {
        // Arrange
        $body = [
            'client_id' => (string) $this->client->id,
            'client_secret' => $this->client->secret,
            'email' => 'test@test.test',
            'password' => 'secret',
        ];
        // Act
        $this->json('POST', '/api/signin', $body, ['Accept' => 'application/json'])
        // Assert
        ->assertStatus(200)
        ->assertJsonStructure([
            'data' => [
                'jwt' => [
                    'access_token',
                    'expires_in',
                    'token_type',
                ]
            ],
            'errors'
        ]);
    }
}

Моя удобная аутентификация с паспортом для тестирования

маршруты / api.php

Route::post('/signin', function () {
    $args = request()->only(['email', 'password', 'client_id', 'client_secret']);
    request()->request->add([
        'grant_type' => 'password',
        'client_id' => $args['client_id'] ?? env('PASSPORT_CLIENT_ID', ''),
        'client_secret' => $args['client_secret'] ?? env('PASSPORT_CLIENT_SECRET', ''),
        'username' => $args['email'],
        'password' => $args['password'],
        'scope' => '*',
    ]);
    $res = Route::dispatch(Request::create('oauth/token', 'POST'));
    $data = json_decode($res->getContent());
    $isOk = $res->getStatusCode() === 200;
    return response()->json([
        'data' => $isOk ? [ 'jwt' => $data ] : null,
        'errors' => $isOk ? null : [ $data ]
    ], 200);
});

Ответы [ 5 ]

0 голосов
/ 04 апреля 2019

Я думаю, что выбранный ответ, вероятно, самый надежный и лучший здесь, но я хотел предоставить альтернативу, которая сработала бы для меня, если вам просто нужно быстро получить тесты, проходящие за паспортом без большой настройки.

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

Вот полный тестовый пример, в котором мне нужно принять пользователя, POST к конечной точке, и использовать его токен авторизации для выполнения запроса.

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

use App\Models\User;
use Laravel\Passport\Passport;

class MyTest extends TestCase
{
    use WithFaker, RefreshDatabase;

    public function my_test()
    {
        /**
        *
        * Without Artisan call you will get a passport 
        * "please create a personal access client" error
        */
        \Artisan::call('passport:install');

        $user = factory(User::class)->create();
        Passport::actingAs($user);

        //See Below
        $token = $user->generateToken();

        $headers = [ 'Authorization' => 'Bearer $token'];
        $payload = [
            //...
        ];



        $response = $this->json('POST', '/api/resource', $payload, $headers);

        $response->assertStatus(200)
                ->assertJson([
                    //...
                ]);

    }
}

И для ясности, вот метод generateToken() в модели User, который использует черту HasApiTokens.

public function generateToken() {
    return $this->createToken('my-oauth-client-name')->accessToken; 
}

Это довольно грубо и готово, на мой взгляд. Например, если вы используете черту RefreshDatabase, вы должны запускать команду passport: install, как это, в каждом методе. Возможно, есть лучший способ сделать это через глобальную настройку, но я довольно плохо знаком с PHPUnit, поэтому я так и делаю (пока).

0 голосов
/ 09 мая 2018

Вот как вы можете реализовать это, чтобы она действительно работала.

Прежде всего, вы должны правильно реализовать db: seed и Паспорт установки .

Второй, вам не нужно, чтобы создать свой собственный маршрут, чтобы проверить, работает ли он (базовые Паспорт ответы достаточно хороши для этого).

Итак, вот описание того, как это работало в моей установке (Laravel 5.5) ...

В моем случае мне нужен только один Паспорт клиент, поэтому я создал другой маршрут для API-авторизации (api/v1/login), чтобы указать только имя пользователя и пароль. Подробнее об этом можно прочитать здесь .

К счастью, этот пример охватывает базовую авторизацию паспорта тест также.

Итак, чтобы успешно запустить ваши тесты, основная идея:

  1. Создание ключей паспорта при настройке теста.
  2. Seed db с пользователями, ролями и другими ресурсами, которые могут понадобиться.
  3. Создать .env запись с PASSPORT_CLIENT_ID (необязательно - Паспорт всегда создавать password grant token с id = 2 на пустом дБ).
  4. Используйте этот идентификатор для получения правильного client_secret из базы данных.
  5. А затем запустите свои тесты ...

Примеры кода ...

ApiLoginTest.php

/**
* @group apilogintests
*/    
public function testApiLogin() {
    $body = [
        'username' => 'admin@admin.com',
        'password' => 'admin'
    ];
    $this->json('POST','/api/v1/login',$body,['Accept' => 'application/json'])
        ->assertStatus(200)
        ->assertJsonStructure(['token_type','expires_in','access_token','refresh_token']);
}
/**
 * @group apilogintests
 */
public function testOauthLogin() {
    $oauth_client_id = env('PASSPORT_CLIENT_ID');
    $oauth_client = OauthClients::findOrFail($oauth_client_id);

    $body = [
        'username' => 'admin@admin.com',
        'password' => 'admin',
        'client_id' => $oauth_client_id,
        'client_secret' => $oauth_client->secret,
        'grant_type' => 'password',
        'scope' => '*'
    ];
    $this->json('POST','/oauth/token',$body,['Accept' => 'application/json'])
        ->assertStatus(200)
        ->assertJsonStructure(['token_type','expires_in','access_token','refresh_token']);
}

Примечания:

Учетные данные должны быть изменены, конечно.

PASSPORT_CLIENT_ID должен быть равен 2, как описано выше.

Проверка JsonStructure является избыточной, поскольку мы получаем 200 ответов, только если авторизация прошла успешно. Однако, если вы хотели дополнительную проверку, это также проходит ...

TestCase.php

public function setUp() {
    parent::setUp();
    \Artisan::call('migrate',['-vvv' => true]);
    \Artisan::call('passport:install',['-vvv' => true]);
    \Artisan::call('db:seed',['-vvv' => true]);
}

Примечания:

Здесь мы создаем соответствующие записи в БД, которые необходимы в наших тестах. Так что помните, чтобы пользователи с ролями и т. Д. Были засеяны здесь.

Заключительные замечания ...

Этого должно быть достаточно, чтобы ваш код работал. В моей системе все это становится зеленым, а также работает на моем Gitlab CI Runner.

Наконец, проверьте также ваше промежуточное ПО на маршрутах. Особенно, если вы экспериментировали с пакетом dingo (или jwt by thymon ).

Единственное промежуточное программное обеспечение, которое вы можете рассмотреть, применяя Паспорт маршрут авторизации, - throttle, чтобы иметь некоторую защиту от атаки грубой силы .

Примечание ...

Паспорт и dingo имеют совершенно разные реализации jwt .

В моих тестах только Паспорт ведет себя правильно, и я предполагаю, что это причина, почему dingo больше не поддерживается.

Надеюсь, это решит вашу проблему ...

0 голосов
/ 07 мая 2018

Я не был знаком с инструментом Passport, на который ссылается Дуайт, когда писал это, поэтому возможно, что это более простое решение. Но вот кое-что, что может помочь. Он создает для вас токен, который вы затем можете применить к своему вызову mock-API.

/**
 * @param Authenticatable $model
 * @param array $scope
 * @param bool $personalAccessToken
 * @return mixed
 */
public function makeOauthLoginToken(Authenticatable $model = null, array $scope = ['*'], $personalAccessToken = true)
{
    $tokenName = $clientName = 'testing';
    Artisan::call('passport:client', ['--personal' => true, '--name' => $clientName]);
    if (!$personalAccessToken) {
        $clientId = app(Client::class)->where('name', $clientName)->first(['id'])->id;
        Passport::$personalAccessClient = $clientId;
    }
    $userId = $model->getKey();
    return app(PersonalAccessTokenFactory::class)->make($userId, $tokenName, $scope)->accessToken;
}

Тогда вы просто примените его к заголовкам:

$user = app(User::class)->first($testUserId);
$token = $this->makeOauthLoginToken($user);
$headers = ['authorization' => "Bearer $token"];
$server = $this->transformHeadersToServerVars($headers);

$body = $cookies = $files = [];
$response = $this->call($method, $uri, $body, $cookies, $files, $server);

$content = $response->getContent();
$code = $response->getStatusCode();

Если вам нужно разобрать токен, попробуйте следующее:

/**
 * @param string $token
 * @param Authenticatable $model
 * @return Authenticatable|null
 */
public function parsePassportToken($token, Authenticatable $model = null)
{
    if (!$model) {
        $provider = config('auth.guards.passport.provider');
        $model = config("auth.providers.$provider.model");
        $model = app($model);
    }
    //Passport's token parsing is looking to a bearer token using a protected method.  So a dummy-request is needed.
    $request = app(Request::class);
    $request->headers->add(['authorization' => "Bearer $token"]);
    //Laravel\Passport\Guards\TokenGuard::authenticateViaBearerToken() expects the user table to leverage the
    //HasApiTokens trait.  If that's missing, a query macro can satisfy its expectation for this method.
    if (!method_exists($model, 'withAccessToken')) {
        Builder::macro('withAccessToken', function ($accessToken) use ($model) {
            $model->accessToken = $accessToken;
            return $this;
        });
        /** @var TokenGuard $guard */
        $guard = Auth::guard('passport');
        return $guard->user($request)->getModel();
    }
    /** @var TokenGuard $guard */
    $guard = Auth::guard('passport');
    return $guard->user($request);
}
0 голосов
/ 08 мая 2018

Для проверки паспорта вам не нужно указывать реального пользователя и пароль, вы можете создать тестовый.
Вы можете использовать Passport::actingAs или setup().

Для actingAs вы можете сделать как

public function testServerCreation()
{
    Passport::actingAs(
        factory(User::class)->create(),
        ['create-servers']
    );

    $response = $this->post('/api/create-server');

    $response->assertStatus(200);
}

и с setUp() вы можете достичь этого с помощью

public function setUp()
    {
        parent::setUp();
        $clientRepository = new ClientRepository();
        $client = $clientRepository->createPersonalAccessClient(
            null, 'Test Personal Access Client', $this->baseUrl
        );
        DB::table('oauth_personal_access_clients')->insert([
            'client_id' => $client->id,
            'created_at' => new DateTime,
            'updated_at' => new DateTime,
        ]);
        $this->user = factory(User::class)->create();
        $token = $this->user->createToken('TestToken', $this->scopes)->accessToken;
        $this->headers['Accept'] = 'application/json';
        $this->headers['Authorization'] = 'Bearer '.$token;
    }

Вы можете получить более подробную информацию Здесь и https://laravel.com/docs/5.6/passport#testing.

0 голосов
/ 01 мая 2018

Laravel Passport на самом деле поставляется с некоторыми помощниками по тестированию , которые вы можете использовать для проверки ваших аутентифицированных конечных точек API.

Passport::actingAs(
    factory(User::class)->create(),
);
...