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
相关产品推荐
相关产品推荐

