Laravel Passport::actingAs测试报错Route [login]未定义问题排查
问题:Laravel Passport路由同时支持客户端令牌与用户令牌访问的测试异常
问题场景
- 使用Postman时,目标路由支持两种访问方式:
- 通过
/oauth/token获取的客户端授权令牌访问 - 通过用户登录后的个人访问令牌访问
- 通过
- 编写测试时:
- 模拟获取用户令牌的测试(
test_get_products_using_authenticated_user)执行正常 - 使用
Passport::actingAs模拟用户认证的测试(test_products_using_authenticated_user2)抛出错误:Route [login] not defined
- 模拟获取用户令牌的测试(
- 将路由中间件改为
auth:api后测试正常,但业务需求要求路由同时支持客户端授权访问
相关代码与配置
测试代码
public function test_get_products_using_authenticated_user(): void { $data = [ 'email' => CustomerSeeder::CUSTOMER_EXAMPLE_EMAIL, 'password' => CustomerSeeder::CUSTOMER_EXAMPLE_PASSWORD, ]; $loginResponse = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->generateAccessToken(), ])->postJson('/api/login', $data); $loginResponse->assertStatus(200); $loginToken = $loginResponse->json('data.token'); $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $loginToken, ])->getJson('/api/products'); $response = $this->get('/api/products'); // 冗余代码,无令牌会导致失败 $response->assertStatus(200); } public function test_products_using_authenticated_user2(): void { $user = User::where('email', CustomerSeeder::CUSTOMER_EXAMPLE_EMAIL)->first(); Passport::actingAs( $user, [], 'api' ); $response = $this->get('/api/products'); $response->assertStatus(200); }
报错信息
Route [login] not defined. at tests\Feature\api\ProductTest.php:58 54▕ 'api' 55▕ ); 56▕ 57▕ $response = $this->get('/api/products'); ➜ 58▕ $response->assertStatus(200); 59▕ } 60▕ } 61▕
产品路由配置
Route::group(['prefix' => 'products', 'middleware' => 'client'], function () { Route::get('/', [ProductController::class, 'index'])->name('product.list'); });
Http/Kernel.php配置
protected $middlewareAliases = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'client' => CheckClientCredentials::class, ];
config/auth.php配置
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'passport', 'provider' => 'users', ], ], 'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\Models\User::class, ], ],
解决方案
问题根源
路由使用的client中间件(CheckClientCredentials)仅验证客户端凭证,无法识别Passport::actingAs模拟的用户认证状态。当请求未通过客户端凭证校验时,Laravel默认会尝试重定向到login路由,但API场景下未定义该路由,因此报错。
要实现路由同时支持两种认证方式,需要自定义中间件实现「用户认证或客户端认证二选一」的逻辑。
步骤1:创建自定义中间件
执行命令生成中间件:
php artisan make:middleware CheckApiAuthentication
编写中间件逻辑:
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; use Laravel\Passport\Http\Middleware\CheckClientCredentials; use Illuminate\Auth\Middleware\Authenticate as AuthenticateMiddleware; class CheckApiAuthentication { protected $clientMiddleware; protected $authMiddleware; public function __construct(CheckClientCredentials $clientMiddleware, AuthenticateMiddleware $authMiddleware) { $this->clientMiddleware = $clientMiddleware; $this->authMiddleware = $authMiddleware->setDefaultGuard('api'); } public function handle(Request $request, Closure $next): Response { try { // 优先尝试用户令牌认证(auth:api) return $this->authMiddleware->handle($request, $next); } catch (\Exception $e) { // 用户认证失败,尝试客户端凭证认证 try { return $this->clientMiddleware->handle($request, $next); } catch (\Exception $clientException) { // 两种认证均失败,返回401未授权 return response()->json(['message' => 'Unauthorized'], Response::HTTP_UNAUTHORIZED); } } } }
步骤2:注册中间件
在Http/Kernel.php的middlewareAliases中添加自定义中间件别名:
protected $middlewareAliases = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'client' => CheckClientCredentials::class, 'api.auth' => \App\Http\Middleware\CheckApiAuthentication::class, // 新增 ];
步骤3:修改路由使用自定义中间件
Route::group(['prefix' => 'products', 'middleware' => 'api.auth'], function () { Route::get('/', [ProductController::class, 'index'])->name('product.list'); });
步骤4:修正测试冗余代码
删除test_get_products_using_authenticated_user中冗余的无令牌请求:
// 移除这行 // $response = $this->get('/api/products');
内容的提问来源于stack exchange,提问作者Kelv1nG
相关产品推荐
相关产品推荐

