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

Laravel Passport客户端中间件Scope或逻辑授权失败问题

Laravel Passport 客户端任一作用域权限配置方案

问题说明

使用client:admin,view中间件时,要求客户端必须同时拥有所有指定作用域才能访问接口,但实际需求是客户端拥有任一指定作用域即可访问。尝试嵌套scope中间件的写法始终返回未授权。

当前代码与配置

未生效的路由写法

Route::group(['prefix'=>'logs','as'=>'logs.','middleware' => 'client'], function(){
    Route::middleware(['scope:admin,store'])->group(function () {
        Route::post('/', [LogController::class, 'store'])->name('store');
    });

    Route::middleware(['scope:admin,view'])->group(function () {
        Route::get('/', [LogController::class, 'index'])->name('all');
        Route::get('/date/{date}', [LogController::class, 'findByDate'])->name('date');
        Route::get('/table/{table}/{id_table}', [LogController::class, 'findByTableAndId'])->name('table.id');
        Route::get('/action/{action}', [LogController::class, 'findByAction'])->name('action');
        Route::get('/message', [LogController::class, 'findByMessage'])->name('message');
        Route::get('/error', [LogController::class, 'findByError'])->name('error');
        Route::get('/locations', [LogsLocationController::class, 'index'])->name('locations');
    });
});

相关配置文件

AppServiceProvider.php

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Passport::hashClientSecrets();
    Passport::tokensExpireIn(now()->addDays(15));
    Passport::refreshTokensExpireIn(now()->addDays(30));
    Passport::tokensCan([
        'admin' => 'Administrator',
        'store' => 'Store logs',
        'put' => 'Update logs',
        'delete' => 'Delete logs',
        'view' => 'View logs',
    ]);
}

Kernel.php(中间件别名)

protected $middlewareAliases = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
    'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
    'can' => \Illuminate\Auth\Middleware\Authorize::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
    'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
    'signed' => \App\Http\Middleware\ValidateSignature::class,
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
    'client' => CheckClientCredentials::class,
    'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
    'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,
];

config/auth.php

'defaults' => [
    'guard' => 'api',
    'passwords' => 'users',
],

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
    'api' => [
        'driver' => 'passport',
        'provider' => 'users',
    ],
], 

解决方案:自定义客户端任一作用域检查中间件

原生client中间件要求客户端拥有所有指定作用域,而scope系列中间件是针对用户授权Token设计的,不适用于客户端凭证模式。需自定义中间件实现客户端任一作用域的校验逻辑。

步骤1:创建自定义中间件

执行Artisan命令生成中间件:

php artisan make:middleware CheckClientAnyScope

步骤2:编写中间件逻辑

修改app/Http/Middleware/CheckClientAnyScope.php文件:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Auth\AuthenticationException;
use Laravel\Passport\Client;
use Laravel\Passport\Exceptions\MissingScopeException;
use Laravel\Passport\Http\Middleware\CheckClientCredentials;

class CheckClientAnyScope extends CheckClientCredentials
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  mixed  ...$scopes
     * @return mixed
     *
     * @throws \Illuminate\Auth\AuthenticationException
     * @throws \Laravel\Passport\Exceptions\MissingScopeException
     */
    public function handle($request, Closure $next, ...$scopes)
    {
        $client = $this->validateClient($request);

        if (! $client instanceof Client) {
            throw new AuthenticationException;
        }

        // 校验客户端是否拥有任一指定作用域
        $hasAnyScope = collect($scopes)->intersect($client->scopes)->isNotEmpty();

        if (! $hasAnyScope) {
            throw new MissingScopeException($scopes);
        }

        return $next($request);
    }
}

步骤3:注册中间件别名

在Kernel.php的middlewareAliases数组中添加自定义中间件的别名:

protected $middlewareAliases = [
    // ... 其他原有别名
    'client.any' => \App\Http\Middleware\CheckClientAnyScope::class, // 新增此行
];

步骤4:修改路由使用自定义中间件

Route::group(['prefix'=>'logs','as'=>'logs.'], function(){
    // 客户端拥有admin或store作用域即可访问
    Route::middleware(['client.any:admin,store'])->group(function () {
        Route::post('/', [LogController::class, 'store'])->name('store');
    });

    // 客户端拥有admin或view作用域即可访问
    Route::middleware(['client.any:admin,view'])->group(function () {
        Route::get('/', [LogController::class, 'index'])->name('all');
        Route::get('/date/{date}', [LogController::class, 'findByDate'])->name('date');
        Route::get('/table/{table}/{id_table}', [LogController::class, 'findByTableAndId'])->name('table.id');
        Route::get('/action/{action}', [LogController::class, 'findByAction'])->name('action');
        Route::get('/message', [LogController::class, 'findByMessage'])->name('message');
        Route::get('/error', [LogController::class, 'findByError'])->name('error');
        Route::get('/locations', [LogsLocationController::class, 'index'])->name('locations');
    });
});

原写法失效原因

原生scope中间件是检查用户授权Token的作用域,而客户端凭证模式下,请求使用的是客户端自身的凭证,此时Auth::user()返回的是客户端实例而非用户模型,导致scope中间件无法正确读取作用域信息。自定义的client.any中间件直接针对客户端实例校验其绑定的作用域,符合客户端凭证模式的需求。

内容的提问来源于stack exchange,提问作者Roberto Cavalcante

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 19:05:34