Laravel 5.5(Auth):多设备“记住我”及设备管理实现问询
我之前刚好实现过类似的多设备登录管理功能,结合你的需求,分享下我的实战思路和安全建议,应该能帮到你!
核心思路:从单token到多token存储
默认Laravel把remember_token存在users表的单字段里,这就是为什么只能记住一个设备的原因。我们需要把每个设备的登录令牌单独存储,所以第一步是新建一个专门的表来保存多设备的记住令牌。
1. 创建多令牌存储表
先跑一个迁移,创建user_remember_tokens表,用来记录每个设备的登录信息:
Schema::create('user_remember_tokens', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->string('token', 60)->unique(); // 和Laravel默认token长度一致 $table->text('device_info')->nullable(); // 存储设备标识,方便用户识别 $table->timestamp('expires_at'); // 令牌过期时间 $table->timestamps(); });
同时在User模型里添加关联:
public function rememberTokens() { return $this->hasMany(UserRememberToken::class); }
2. 重写自定义UserProvider的关键方法
你已经继承了EloquentUserProvider,接下来重写两个方法来适配多token逻辑:
① retrieveByToken:根据令牌查找用户
不再去users表查单字段,而是去新表找匹配且未过期的令牌:
public function retrieveByToken($identifier, $token) { // 注意:Laravel默认会对remember_token做sha256哈希存储,所以要匹配哈希后的值 $tokenRecord = DB::table('user_remember_tokens') ->where('user_id', $identifier) ->where('token', hash('sha256', $token)) ->where('expires_at', '>', now()) ->first(); if (!$tokenRecord) { return null; } return $this->createModel()->newQuery()->find($identifier); }
② updateRememberToken:生成新令牌并存储
默认逻辑是覆盖users表的token,现在改为插入新的令牌记录,同时记录设备信息:
public function updateRememberToken(UserContract $user, $token) { // 如果token为空,说明用户取消了"记住我",可以按需清理该设备的令牌 if (empty($token)) { // 这里可以根据会话关联的token删除,或者暂时留着过期自动清理 return; } // 用Laravel默认的有效期配置(默认5年) $expiresAt = now()->addDays(config('auth.remember_expire', 1825)); // 记录设备信息,比如User-Agent,方便用户识别是哪个设备 $deviceInfo = request()->header('User-Agent'); // 可以用jenssegers/agent包解析成更友好的名称,比如"Chrome on Windows 10" DB::table('user_remember_tokens')->insert([ 'user_id' => $user->getAuthIdentifier(), 'token' => hash('sha256', $token), 'device_info' => $deviceInfo, 'expires_at' => $expiresAt, 'created_at' => now(), 'updated_at' => now(), ]); }
3. 实现设备管理页面
现在用户的每个登录设备都有对应的令牌记录了,接下来做一个页面让用户查看和注销设备:
控制器方法示例
// 展示已登录设备列表 public function showDevices() { $tokens = auth()->user()->rememberTokens() ->orderBy('created_at', 'desc') ->get(); return view('user.devices', compact('tokens')); } // 注销指定设备 public function revokeDevice($tokenId) { // 确保只能注销自己的设备 $token = auth()->user()->rememberTokens()->findOrFail($tokenId); $token->delete(); // 可选:同时销毁该设备对应的会话(如果用数据库会话驱动) DB::table('sessions') ->where('user_id', auth()->id()) // 排除当前会话,避免把自己踢下线 ->where('id', '!=', request()->session()->getId()) ->delete(); return back()->with('success', '该设备已成功注销'); }
前端视图示例(简化版)
<div class="device-list"> @foreach($tokens as $token) <div class="device-item"> <p>{{ $token->device_info }}</p> <p>登录时间:{{ $token->created_at->format('Y-m-d H:i') }}</p> <form action="{{ route('user.devices.revoke', $token->id) }}" method="POST"> @csrf @method('DELETE') <button type="submit">注销该设备</button> </form> </div> @endforeach </div>
4. 安全与优化建议
- 令牌唯一性:始终用
Str::random(60)生成令牌(Laravel默认逻辑),确保足够随机唯一。 - 过期清理:定期用Laravel任务调度清理过期的令牌记录,避免表数据冗余:
$schedule->call(function () { DB::table('user_remember_tokens') ->where('expires_at', '<', now()) ->delete(); })->daily(); - 设备识别优化:不要直接显示原始的User-Agent,用
jenssegers/agent包解析成用户友好的设备名称,提升体验。 - 会话同步注销:如果用户注销某个设备,除了删除令牌,最好同时销毁对应的会话,避免对方还在会话有效期内继续访问。
- CSRF保护:设备注销的表单一定要加
@csrf令牌,防止跨站请求伪造。 - 同一设备去重:如果用户在同一设备多次勾选“记住我”,可以考虑删除该设备之前的令牌,避免同一设备多条冗余记录。
内容的提问来源于stack exchange,提问作者Maxime
相关产品推荐
相关产品推荐

