Laravel升级后CheckPasswordStatus中间件执行顺序错误致空对象调用异常
Laravel7升级到9+Backpack4升级到5后中间件执行顺序问题解决
问题描述
升级Laravel从7到9、Backpack从4到5后,未登录状态下访问后台路由(如example.com/admin/contacts)时,本该跳转到登录页example.com/admin/login,却抛出错误Call to a member function can() on null,说明自定义的CheckPasswordStatus中间件在auth中间件之前执行,导致未登录时用户为null,后续代码调用can()方法报错。
相关配置信息
1. Kernel.php中的中间件声明
protected $middlewareGroups = [ 'web' => [ \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, ], 'admin' => [ CheckPasswordStatus::class, ], 'api' => [ 'throttle:5000,10', \Illuminate\Routing\Middleware\SubstituteBindings::class, ], ]; protected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'auth.basic.source' => \App\Http\Middleware\AuthenticateWithBasicAuthForSources::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, 'signed' => \App\Http\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, ];
2. routes/backpack/custom.php中的路由声明
Route::prefix(config('backpack.base.route_prefix', 'admin'))->middleware([backpack_middleware(), config('backpack.base.web_middleware', 'web')])->group(function () { // custom admin routes Route::crud('contact', ContactCrudController::class); Route::crud('conflicts', IncomingImportConflictCrudController::class); Route::patch('contacts/{id}/advertising-rejection', [ContactCrudController::class, 'advertisingRejection'])->name('contact.advertising_rejection'); Route::post('conflicts/{id}/new', [IncomingImportConflictCrudController::class, 'creatNewContact'])->name('new-contact'); Route::get('conflicts/{id}/compare/{compareId}', [IncomingImportConflictCrudController::class, 'compare']); });
3. config/backpack/base.php配置
'middleware_key' => 'admin'
4. CheckPasswordStatus中间件代码
public function handle($request, Closure $next) { if ($user = $request->user()) { if ($user->passwordIsExpired()) { return redirect()->route('password.reset.manually'); } } return $next($request); }
尝试过的操作及结果
- 将
middleware_key设为未定义值something时,可正常跳转到登录页; - 恢复
middleware_key为admin但注释Kernel.php中的admin中间件组,功能正常(但CheckPasswordStatus中间件失效); - 使用
web作为middleware_key时,出现相同错误且无跳转; - 尝试两种Stack Overflow方案:在
CheckPasswordStatus中使用authenticated方法,提示“CheckPasswordStatus middleware not callable”;使用Auth::check()作为条件,页面空白无跳转。
解决方案
方案1:调整admin中间件组顺序,确保auth先执行
修改Kernel.php的admin中间件组,将auth中间件放在CheckPasswordStatus之前,这样未登录时会先触发auth跳转,登录后再检查密码状态:
protected $middlewareGroups = [ // ... 其他中间件组保持不变 'admin' => [ \App\Http\Middleware\Authenticate::class, // 先执行登录验证 CheckPasswordStatus::class, // 再执行密码状态检查 ], ];
保持config/backpack/base.php的middleware_key为admin即可。
方案2:修正路由中间件执行顺序
修改路由文件中的中间件顺序,让backpack_middleware()(默认包含auth)和web中间件先执行,再加载admin中间件组:
Route::prefix(config('backpack.base.route_prefix', 'admin')) ->middleware([config('backpack.base.web_middleware', 'web'), backpack_middleware(), 'admin']) ->group(function () { // 自定义后台路由 });
这样保证会话、登录验证流程先完成,再执行密码状态检查。
方案3:强化中间件的未登录兼容(兜底)
虽然你的中间件已经判断了用户是否存在,但错误的can()调用可能来自后续逻辑,可进一步确保未登录时直接放行给auth中间件处理:
public function handle($request, Closure $next) { // 仅当用户已通过认证时,才执行密码状态检查 if (Auth::check()) { $user = Auth::user(); if ($user->passwordIsExpired()) { return redirect()->route('password.reset.manually'); } } return $next($request); }
同时确保use Illuminate\Support\Facades\Auth;在中间件顶部引入。
内容的提问来源于stack exchange,提问作者LBR
相关产品推荐
相关产品推荐

