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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 03:32:53