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事件,在令牌过期时自动解锁记录:
- 创建事件监听器
php artisan make:listener UnlockAdmissionRecordsOnTokenExpired
- 编写监听器逻辑
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]); } }
- 在
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
相关产品推荐
相关产品推荐

