如何使用 Laravel Passport 通过 API 测试身份验证?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/50113508/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to test authentication via API with Laravel Passport?
提问by dddenis
I'm trying to test the authentication with Laravel's Passport and there's no way... always received a 401 of that client is invalid, I'll leave you what I've tried:
我正在尝试使用 Laravel 的 Passport 测试身份验证,但没有办法......总是收到该客户端的 401 无效,我会告诉你我尝试过的:
My phpunit configuration is the one that comes from base with laravel
我的 phpunit 配置是来自 laravel 的基础配置
tests/TestCase.php
测试/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' => '[email protected]',
'password' => bcrypt('secret')
]);
$this->token = $this->user->createToken('TestToken', [])->accessToken;
}
}
tests/Feature/AuthTest.php
测试/功能/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' => '[email protected]',
'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'
]);
}
}
My handy authentication with passport for testing purposes
我使用护照进行方便的身份验证以进行测试
routes/api.php
路线/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);
});
回答by Bart
This is how you can implement this, to make it actually work.
这就是您如何实现这一点,使其真正发挥作用。
First of all, you should properly implement db:seedsand Passport installation.
首先,您应该正确实施db:seeds和Passport 安装。
Second one, you don't need, to create your own route to verify, if that works (basic Passportresponses are fine enough, for that).
第二个,您不需要创建自己的路线来验证是否有效(基本Passport响应就足够了)。
So here is a description, on how it worked in my installation (Laravel 5.5)...
所以这里是一个描述,它是如何在我的安装(Laravel 5.5)中工作的......
In my case, I need only one Passportclient, that's why, I created another route, for api authorization (api/v1/login
), to only supply username and password. You can read more about it here.
就我而言,我只需要一个Passport客户端,这就是为什么我创建了另一条路线,用于 api 授权 ( api/v1/login
),仅提供用户名和密码。您可以在此处阅读更多相关信息。
Fortunately this example covers basic Passport authorizationtest also.
幸运的是,这个例子也涵盖了基本的Passport 授权测试。
So to successfully run your tests, the basic idea is:
因此,要成功运行您的测试,基本思想是:
- Create passport keys on test setup.
- Seed db with users, roles and other resources which might be needed.
- Create
.env
entry withPASSPORT_CLIENT_ID
(optional - Passportalways createpassword grant token
with id = 2 on empty db). - Use this id to fetch proper client_secret from db.
- And then run your tests...
- 在测试设置上创建护照密钥。
- 使用用户、角色和其他可能需要的资源来播种数据库。
- 创建
.env
条目PASSPORT_CLIENT_ID
(可选 - Passport总是password grant token
在空数据库上创建id = 2)。 - 使用此 ID 从数据库中获取正确的 client_secret。
- 然后运行你的测试......
Code examples...
代码示例...
ApiLoginTest.php
ApiLoginTest.php
/**
* @group apilogintests
*/
public function testApiLogin() {
$body = [
'username' => '[email protected]',
'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' => '[email protected]',
'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']);
}
Notes:
笔记:
Credentials need to be modified of course.
当然,凭据需要修改。
PASSPORT_CLIENT_ID needs to be 2, as explained before.
如前所述,PASSPORT_CLIENT_ID 需要为 2。
JsonStructure verification is redundant, since we get 200 response, only if authorization succeeds. However, if you wanted additional verification, this also passes...
JsonStructure 验证是多余的,因为我们得到 200 响应,只有在授权成功时。但是,如果您想要额外的验证,这也会通过...
TestCase.php
测试用例.php
public function setUp() {
parent::setUp();
\Artisan::call('migrate',['-vvv' => true]);
\Artisan::call('passport:install',['-vvv' => true]);
\Artisan::call('db:seed',['-vvv' => true]);
}
Notes:
笔记:
Here we are creating relevant entries to db, which are needed in our tests. So remember, to have users with roles etc. seeded here.
在这里,我们正在为 db 创建相关条目,这在我们的测试中是必需的。所以请记住,在此处播种具有角色等的用户。
Final notes...
最后的笔记...
This should be enough, to get your code working. On my system, all this passes green and also works on my gitlab CI runner.
这应该足以让您的代码工作。在我的系统上,所有这些都通过绿色并且也适用于我的 gitlab CI runner。
Finally, please check your middlewares on routes also. Especially, if you were experimenting with dingo(or jwt by thymon) package.
最后,请检查您的路由中间件。特别是,如果您正在尝试使用dingo(或thymon 的 jwt)包。
The only middleware, you may consider, applying to Passportauthorization route, is throttle
to have some protection from brute force attack.
唯一的中间件,你可以考虑,应用于Passport授权路由,是throttle
对暴力攻击有一些保护。
Side note...
边注...
Passportand dingohave totally different jwtimplementations.
Passport和dingo具有完全不同的jwt实现。
In my tests, only Passportbehaves the right way and I assume, that this is the reason, why dingois not maintained anymore.
在我的测试中,只有Passport 的行为是正确的,我认为这就是为什么不再维护dingo的原因。
Hope it will solve your problem...
希望它能解决你的问题...
回答by Dwight
Laravel Passport actually ships with some testing helperswhich you can use to test your authenticated API endpoints.
Laravel Passport实际上附带了一些测试助手,您可以使用它们来测试经过身份验证的 API 端点。
Passport::actingAs(
factory(User::class)->create(),
);
回答by Bibhudatta Sahoo
For Testing passport you did not need to go for real user and password you can create test one.
You can use Passport::actingAs
or by setup()
.
对于测试护照,您不需要输入真实用户和密码,您可以创建测试护照。
您可以使用 Passport::actingAs
或setup()
。
For actingAs
you can do like
因为actingAs
你可以像
public function testServerCreation()
{
Passport::actingAs(
factory(User::class)->create(),
['create-servers']
);
$response = $this->post('/api/create-server');
$response->assertStatus(200);
}
and with setUp()
you can achieve this by
与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;
}
You can get more details Hereand https://laravel.com/docs/5.6/passport#testing.
您可以在此处和https://laravel.com/docs/5.6/passport#testing获取更多详细信息 。
回答by Eli Hooten
I think the selected answer is likely the most robust and best here so far, but I wanted to provide an alternative that worked for me if you just need to quickly get tests passing behind passport without a lot of setup.
我认为所选的答案可能是迄今为止最强大和最好的答案,但如果您只需要快速让测试通过护照而无需进行大量设置,我想提供一个对我有用的替代方案。
Important note: I think if you're going to do a lot of this, this isn't the right way and other answers are better. But in my estimation this does seem to just work
重要提示:我认为如果你要做很多这样的事情,这不是正确的方法,其他答案会更好。但在我看来,这似乎确实有效
Here's a full test case where I need to assume a user, POST to an endpoint, and use their Authorization token to make the request.
这是一个完整的测试用例,我需要假设一个用户,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([
//...
]);
}
}
And for clarity, here is the generateToken()
method in the User model, which leverages the HasApiTokens
trait.
为了清楚起见,这里是generateToken()
User 模型中的方法,它利用了HasApiTokens
trait。
public function generateToken() {
return $this->createToken('my-oauth-client-name')->accessToken;
}
This is fairly rough and ready in my opinion. For example it if you're using the RefreshDatabase
trait you have to run the passport:install command like this in every method. There may be a better way to do this via global setup, but I'm fairly new to PHPUnit so this is how I'm doing it (for now).
在我看来,这是相当粗糙和准备好的。例如,如果您使用RefreshDatabase
trait,则必须在每种方法中都运行像这样的 Passport:install 命令。通过全局设置可能有更好的方法来做到这一点,但我对 PHPUnit 还很陌生,所以这就是我的做法(目前)。
回答by kmuenkel
I wasn't familiar with the Passport tool that Dwight is referring to when I wrote this, so it's possible that's a simpler solution. But here's something that may help. It produces a token for you, that you can then apply to your mock-API call.
我不熟悉 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;
}
Then you an just apply it to the headers:
然后你只需将它应用于标题:
$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();
If you need to be able to parse the token, try this:
如果您需要能够解析令牌,请尝试以下操作:
/**
* @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);
}
回答by Beefjeff
Testing Personal Access Tokens
测试个人访问令牌
Here is an example for any who are looking to test your api using personal access tokens.
以下是任何希望使用个人访问令牌测试您的 api 的示例。
First, set up the test class
一、设置测试类
protected function setUp(): void
{
parent::setUp();
$this->actingAs(User::first());
$this->access_token = $this->getAccessToken();
}
As for the getAccessToken()
method, just use the Passport frontend api
至于getAccessToken()
方法,直接使用Passport前端api
private function getAccessToken()
{
$response = $this->post('/oauth/personal-access-tokens',[
'name' => 'temp-test-token'
]);
return $response->json('accessToken');
}
And simply:
简单地说:
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);
}
回答by castle
Optimise for unnecessary DB migrations
优化不必要的数据库迁移
Here is an example that ensures that you are still able to write tests that do not depend on the database - not running db migrations.
这是一个示例,可确保您仍然能够编写不依赖于数据库的测试 - 不运行数据库迁移。
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Support\Facades\Schema;
use Laravel\Passport\ClientRepository;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
public function setUp(): void
{
parent::setUp();
if (Schema::hasTable('oauth_clients')) {
resolve(ClientRepository::class)->createPersonalAccessClient(
null, config('app.name') . ' Personal Access Client', config('app.url')
);
}
}
}
Then in your test:
然后在你的测试中:
...
use RefreshDatabase;
/**
* Test login
*
* @return void
*/
public function test_login()
{
$this->withExceptionHandling();
$user = factory(User::class)->create([
'password' => 'secret'
]);
$response = $this->json('POST', route('api.auth.login'), [
'email' => $user->email,
'password' => 'secret',
]);
$response->assertStatus(200);
$response->assertJsonStructure([
//...
]);
}
...
This way you are able to write tests that do not have any db migrations
这样您就可以编写没有任何数据库迁移的测试