涉及概念:
- 这里涉及到 『登录态』的概念,即对已登录的账号记录状态,标识已登录。
实现方案:
- 对已经登录的账号,将状态存储到
Redis
当中并设置有效期(例如:设置30
分钟)。 - 当这个账号再次登录的时候,先判断这个账号在
Redis
是否已经存在状态,如果存在则不让其登录。 - 这种场景下还会涉及『踢登录态』的概念,例如:在另一个地方登录这个账号的时候,是否需要将已经登录的账号,踢掉?
总结:
- 上面的实现方案只是个大概的,具体场景还得具体分析,但是这里涉及的『登录态』的概念是逃不掉的。
- 以上纯属个人见解,不喜勿喷,欢迎撕逼。
简单点 用户名当键 登录时间戳为值 存起来 每次请求携带登陆时间戳,验证这个登陆时间戳是否和当前用户的登陆时间戳一样
(该方法比较原始,但思路比较明确,可能存有思考不当地方,可以自己加以修改)
'-------------------------------------------------------'分割线
比如user现在登陆了 ,缓存用redis。
那么redis::set('user','2019-08-29 10:54:00(你可以存时间戳)','3600(一小时)')。
然后session('user','2019-08-29 10:54:00(你可以存时间戳)')
比如现在user来请求了。那好,你通过user取得session和redis里的登陆时间,然后判断是否一致。
如果一致,那好,说明没有别人更新这个缓存,也就没人用user第二次登陆。
如果redis过期了,也就是一小时到了,此时session确还有效,在续命一个小时就可以了,如果session过期了,直接退出,清空redis缓存。
如果没过期情况下第二个人也用这个user登陆了,那么按照逻辑,
他也会session('user',''2019-08-29 11:24:00(你可以存时间戳)'),redis::set('user','2019-08-29 11:24:00(你可以存时间戳)','3600(一小时)'),此时redis的值被改变了。
按照redis是共享的,所以此时这个redis里的登陆时间就变掉了。那么第一个user过来请求,此时他的session里的登陆时间就和redis里的登陆时间不一样了。因为被第二个user更新了。
如果你不想让第二个人登陆,就在每次登陆时检查redis里是否存在这个user的缓存。
为了保证缓存利用性,要给缓存设置过期时间,和退出清空缓存,请求是发现session过期了,退出登陆时也要清空缓存。
然后上面也说了,缓存过期时间快过session过期,给缓存续命就好了,续命的前提是缓存不存在,而不是缓存存在但登陆时间不同。
避免同时登录的情况有两种
- 踢下线
这种情况比较好处理,只要登录的时候重置下token,那么之前登录状态的账号就失效了
- 提示当前账号已经登录,登录失败
这种情况下,你只要在用户表增加一个登录状态的字段,每次登录之前,判断这个字段是否为登录状态即可
最简单的方法是用户登录成功后把用户的sessionId储存到缓存中,然后再cookie中也存一份sessionId,用户访问的时候,比较这两个sessionId,如果不一样,那就踢下线
我的实现原理:使用浏览器的 session id
来判断,每次用户登录后更新用户的session id
,然后通过中间件来过滤,不知道是不是你想要的效果,这是 5.5 的版本,5.5 以上版本好像自带有这种功能。
中间件
- 新建
app/Http/Middleware/SingleDeviceLogin.php
<?php
namespace App\Http\Middleware;
use Closure;
class SingleDeviceLogin
{
public function handle($request, Closure $next)
{
if (\Auth::check() && \Auth::user()->session_id != session()->getId()) {
\Auth::logout();
abort(422, '您已在别的设备登录');
}
return $next($request);
}
}
- 加入到
app/Http/Kernel.php
中$middlewareGroups
的web
中
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\SingleDeviceLogin::class,
],
];
登录事件
- 新建
app/Listeners/LoginListener.php
<?php
namespace App\Listeners;
use Illuminate\Auth\Events\Login;
use Illuminate\Contracts\Queue\ShouldQueue;
class LoginListener implements ShouldQueue
{
public function handle(Login $event)
{
$update = [
'session_id' => session()->getId(),
];
$event->user->update($update);
}
}
- 在
app/Providers/EventServiceProvider.php
注册监听事件
protected $listen = [
'Illuminate\Auth\Events\Login' => [
'App\Listeners\LoginListener'
],
];
至此,完成
实现避免账户同时登录,核心就是用一张表记录用户会话信息。
用户会话表:会话ID、用户ID、登录状态(已登录、已退出)
当用户在新设备身份验证成功登录时,会产生新会话,登录状态为已登录;同时根据会话记录,将该用户历史的会话记录登录状态从已登录标记为已退出即可。
另一方面,用户登录态检查的中间件,要求登录状态为已登录。
常见的无外乎两种场景。
1.基于cookie+session的用户认证
不管先后登陆成功--->都保存session_id(可以在用户表加一个字段保存or存在redis里)
中间件校验session_id和服务器保存的session_id是否一致。不一致(被挤掉了)销销毁该会话session跳转到登录页。
原理就是后者登录成功会把保存的session_id更新。
2.基于token的用户认证,后者登录成功直接重置token即可。
推荐文章: