如何使用Laravel Passport通过API测试身份验证?

时间:2018-05-01 08:34:34

标签: laravel testing laravel-5 phpunit laravel-passport

我正在尝试使用Laravel的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;
    }
}

测试/特征/ 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);
});

6 个答案:

答案 0 :(得分:12)

这就是你如何实现它,使它真正起作用。

首先,您应该正确实施 db:seeds Passport安装

第二个,您不需要创建自己的路线进行验证,如果可行的话(基本的 Passport 响应就足够了)。

以下是关于它在我的安装中如何工作的描述(Laravel 5.5)......

在我的情况下,我只需要一个 Passport 客户端,这就是为什么我为api授权(api/v1/login)创建了另一条路由,只提供用户名和密码。您可以阅读更多相关信息here

幸运的是,此示例还涵盖了基本的 Passport授权测试。

因此,要成功运行测试,基本思路是:

  1. 在测试设置上创建护照密钥。
  2. 种子数据库包含可能需要的用户,角色和其他资源。
  3. 使用.env创建PASSPORT_CLIENT_ID条目(可选 - Passport 始终在空db上创建password grant token,id = 2。
  4. 使用此ID从db。
  5. 获取正确的client_secret
  6. 然后运行测试......
  7. 代码示例......

    <强> 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]);
    }
    

    注意:

    这里我们正在创建db的相关条目,这些条目在我们的测试中是必需的。 所以请记住,让角色等用户在这里播种。

    最后的笔记......

    这应该足以让您的代码正常运行。在我的系统上,所有这些都通过绿色,也适用于我的gitlab CI跑步者。

    最后,请检查路线上的中间件。特别是,如果您正在尝试 dingo (或 jwt by thymon )包。

    您可以考虑申请 Passport 授权路由的唯一中间件,throttle可以获得暴力攻击的一些保护。

    旁注......

    Passport dingo 具有完全不同的 jwt 实现。

    在我的测试中,只有 Passport 表现正确,我认为这就是为什么 dingo 不再维护的原因。

    希望它能解决你的问题...

答案 1 :(得分:4)

Laravel Passport actually ships with some testing helpers,您可以使用它来测试经过身份验证的API端点。

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

答案 2 :(得分:2)

对于测试护照,您无需使用真实用户和密码即可创建测试版 您可以使用Passport::actingAssetup()

对于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;
    }

您可以获得更多详细信息 Herehttps://laravel.com/docs/5.6/passport#testing

答案 3 :(得分:1)

我不熟悉Dwight在写这篇文章时所指的Passport工具,所以它可能是一个更简单的解决方案。但是这里可能会有所帮助。它会为您生成一个令牌,然后您可以将其应用于您的模拟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);
}

答案 4 :(得分:1)

我认为所选答案到目前为止可能是最可靠,最好的答案,但是我想提供一个对我有用的替代方法,如果您只需要快速进行通过护照的考试而无需太多设置的话。

重要说明:我认为,如果您打算做很多这样的事情,这不是正确的方法,其他答案也更好。但是据我估计,这确实可以正常工作

这是一个完整的测试用例,我需要假设一个用户,将其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([
                    //...
                ]);

    }
}

为清楚起见,这是User模型中的generateToken()方法,它利用了HasApiTokens特性。

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

在我看来,这相当粗糙且已准备就绪。例如,如果您使用的是RefreshDatabase特性,则必须在每种方法中都运行类似的password:install命令。可以通过全局设置来执行此操作,这是更好的方法,但是我对PHPUnit还是很陌生,所以(现在)这就是我的操作方式。

答案 5 :(得分:0)

测试个人访问令牌

这是所有希望使用个人访问令牌测试您的api的示例。

首先,设置测试类

protected function setUp(): void
{
    parent::setUp();
    $this->actingAs(User::first());
    $this->access_token = $this->getAccessToken();
}

对于getAccessToken()方法,只需使用Passport前端api

private function getAccessToken()
{
    $response = $this->post('/oauth/personal-access-tokens',[
        'name' => 'temp-test-token'
    ]);

    return $response->json('accessToken');
}

简单地说:

public function the_personal_access_token_allows_us_to_use_the_api()
{
    $response = $this->get('/api/user', [
        'Authorization' => "Bearer $this->access_token"
    ]);


    $response->assertStatus(200);

}