Laravel中如何获取当前用户的所有登录会话(Redis驱动)
获取Redis驱动下特定用户的所有会话并实现多设备注销
核心思路
Laravel的Redis会话以键值对存储:键为带前缀的会话ID,值为序列化的会话数据。要定位特定用户的会话,要么从会话数据中识别用户ID,要么额外维护用户与会话的映射关系,两种方案分别适用于不同场景。
方法一:遍历解析会话数据(无需额外存储)
适合不想修改现有登录逻辑的场景,通过扫描Redis会话键,逐个解析数据匹配目标用户ID:
1. 确保会话中存储用户ID
如果登录时未主动存入,可在LoginController的authenticated方法中补充:
protected function authenticated(Request $request, $user) { // 将用户ID存入会话,作为后续匹配标识 $request->session()->put('user_id', $user->id); }
2. 扫描并筛选目标用户的会话
使用SCAN命令(比KEYS更高效,避免阻塞Redis)遍历所有会话键,解析数据后筛选:
use Illuminate\Support\Facades\Redis; $targetUserId = 123; // 要查询的目标用户ID $sessionConn = Redis::connection('session'); $sessionPrefix = config('session.prefix', 'laravel:session'); $userSessions = []; $iterator = null; do { // 扫描匹配所有会话键 [$iterator, $keys] = $sessionConn->scan($iterator, 'MATCH', "{$sessionPrefix}:*"); if (!empty($keys)) { foreach ($keys as $key) { $sessionData = $sessionConn->get($key); if (!$sessionData) continue; // 反序列化Laravel会话数据 $sessionPayload = unserialize($sessionData); // 匹配目标用户ID if (isset($sessionPayload['user_id']) && $sessionPayload['user_id'] === $targetUserId) { $userSessions[] = [ 'session_key' => $key, 'remaining_ttl' => $sessionConn->ttl($key), // 会话剩余有效期 'user_agent' => $sessionPayload['HTTP_USER_AGENT'] ?? '未知设备' // 若存了用户代理可显示设备信息 ]; } } } } while ($iterator > 0); // $userSessions 即为该用户的所有会话列表
3. 注销指定设备
直接删除对应Redis会话键即可:
// 传入要注销的会话键 $sessionConn->del($targetSessionKey);
方法二:维护用户-会话映射集合(高效查询)
通过Redis集合存储每个用户的会话键,避免遍历所有会话,大幅提升查询效率:
1. 登录时添加会话到用户集合
在LoginController的authenticated方法中,将当前会话键加入用户专属集合:
protected function authenticated(Request $request, $user) { $request->session()->put('user_id', $user->id); $sessionId = $request->session()->getId(); $fullSessionKey = config('session.prefix', 'laravel:session') . ":{$sessionId}"; // 将会话键存入用户的会话集合 Redis::connection('session')->sadd("user:sessions:{$user->id}", $fullSessionKey); }
2. 会话销毁时移除集合中的条目
监听SessionDestroyed事件,在会话过期或用户主动注销时,从集合中移除对应键:
- 在
EventServiceProvider中注册监听:
protected $listen = [ \Illuminate\Session\Events\SessionDestroyed::class => [ \App\Listeners\CleanUserSessionCollection::class, ], ];
- 创建监听器
CleanUserSessionCollection:
namespace App\Listeners; use Illuminate\Session\Events\SessionDestroyed; use Illuminate\Support\Facades\Redis; class CleanUserSessionCollection { public function handle(SessionDestroyed $event) { $sessionConn = Redis::connection('session'); $sessionData = unserialize($sessionConn->get($event->sessionId)); if (isset($sessionData['user_id'])) { // 从用户集合中移除失效的会话键 $sessionConn->srem("user:sessions:{$sessionData['user_id']}", $event->sessionId); } } }
3. 快速获取用户所有会话
直接读取用户集合即可:
$targetUserId = 123; $userSessionKeys = Redis::connection('session')->smembers("user:sessions:{$targetUserId}"); // 可选:批量获取会话详情 $userSessions = []; foreach ($userSessionKeys as $key) { if ($sessionData = Redis::connection('session')->get($key)) { $payload = unserialize($sessionData); $userSessions[] = [ 'session_key' => $key, 'user_agent' => $payload['HTTP_USER_AGENT'] ?? '未知设备' ]; } }
4. 定期清理失效会话
Redis会自动删除过期会话,但集合中的条目不会同步移除,需定时任务清理:
// 示例:在Console/Kernel.php中添加每日定时任务 protected function schedule(Schedule $schedule) { $schedule->call(function () { $sessionConn = Redis::connection('session'); // 遍历所有用户会话集合(可按需限定用户范围) $collectionKeys = $sessionConn->keys("user:sessions:*"); foreach ($collectionKeys as $key) { $sessionKeys = $sessionConn->smembers($key); foreach ($sessionKeys as $sessionKey) { if (!$sessionConn->exists($sessionKey)) { $sessionConn->srem($key, $sessionKey); } } } })->daily(); }
内容的提问来源于stack exchange,提问作者Atoms
相关产品推荐
相关产品推荐

