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

Laravel会话超时后无法释放用户锁定的admission记录问题

问题:会话超时后无法自动释放Admission记录锁定

我有一张admission数据表,包含locked_by(存储锁定记录的用户ID)和locked_at(锁定时间戳)两个字段,用于标识记录锁定状态。我希望用户会话超时后,自动释放该用户锁定的admission记录,于是做了如下实现,但测试发现会话超时后用户会被登出,但locked_by字段始终未被清空。

现有实现

API路由

Route::group(['middleware' => ['auth:sanctum', 'updateLastActive']], function () {
    // 路由内容
});

updateLastActive中间件代码

class UpdateLastActive
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        if (Auth::check()) {
            $user = Auth::user();
            $sessionLifetime = config('session.lifetime'); // 单位:分钟
            $cutoffTime = Carbon::now()->subMinutes($sessionLifetime);

            // 检查用户会话是否过期
            if ($user->last_active < $cutoffTime) {
                // 解锁该用户锁定的所有记录
                \App\Models\Admission::where('locked_by', $user->id)->update(['locked_by' => null]);

                // 可选:登出用户
                Auth::logout();

                // 重定向到登录页并提示会话过期
                return redirect()->route('login')->withErrors(['message' => 'Your session has expired due to inactivity.']);
            }

            // 会话有效时更新最后活跃时间
            $user->update(['last_active' => now()]);
        }

        return $next($request);

    }
}

问题原因及修复方案

核心问题

当用户会话实际超时后,Sanctum会自动使会话失效,此时Auth::check()直接返回false,中间件里的解锁代码根本不会执行,这就是locked_by字段没被清空的原因。

修复方案

方案1:监听Sanctum令牌过期事件

利用Sanctum自带的TokenExpired事件,在令牌过期时自动解锁记录:

  1. 创建事件监听器
php artisan make:listener UnlockAdmissionRecordsOnTokenExpired
  1. 编写监听器逻辑
namespace App\Listeners;

use Laravel\Sanctum\Events\TokenExpired;
use App\Models\Admission;

class UnlockAdmissionRecordsOnTokenExpired
{
    public function handle(TokenExpired $event)
    {
        // 获取过期令牌对应的用户ID
        $userId = $event->token->tokenable_id;
        
        // 解锁该用户锁定的所有Admission记录
        Admission::where('locked_by', $userId)->update(['locked_by' => null]);
    }
}
  1. 在EventServiceProvider中注册监听器
protected $listen = [
    \Laravel\Sanctum\Events\TokenExpired::class => [
        \App\Listeners\UnlockAdmissionRecordsOnTokenExpired::class,
    ],
];

方案2:调整中间件逻辑,兼容会话过期场景

如果坚持用中间件,可修改逻辑,即使Auth::check()失败,也尝试从请求令牌中获取用户信息处理:

public function handle(Request $request, Closure $next): Response
{
    $user = Auth::user();
    
    // 若用户未通过Auth校验,尝试从请求令牌中获取过期的用户信息
    if (!$user && $request->bearerToken()) {
        $token = \Laravel\Sanctum\PersonalAccessToken::findToken($request->bearerToken());
        if ($token && $token->expired()) {
            $userId = $token->tokenable_id;
            Admission::where('locked_by', $userId)->update(['locked_by' => null]);
            $token->delete(); // 清理过期令牌
            return redirect()->route('login')->withErrors(['message' => 'Your session has expired due to inactivity.']);
        }
    }

    if ($user) {
        $sessionLifetime = config('session.lifetime');
        $cutoffTime = Carbon::now()->subMinutes($sessionLifetime);

        if ($user->last_active < $cutoffTime) {
            Admission::where('locked_by', $user->id)->update(['locked_by' => null]);
            Auth::logout();
            return redirect()->route('login')->withErrors(['message' => 'Your session has expired due to inactivity.']);
        }

        $user->update(['last_active' => now()]);
    }

    return $next($request);
}

额外建议

  • 确保last_active字段在用户每次请求时都能正确更新,避免因字段值异常导致逻辑判断错误。
  • 给locked_by字段添加数据库索引,提升批量更新时的执行效率。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 18:33:10