You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 21:13:22