保证一个账号只有一个用户登录
用户在访问网站时,会与网站建立 Session
,并将 SessionId
存储在 Cookie
,以此作为用户此次会话的凭证。
单用户登录的原理是:在用户登录后,销毁 这个用户之前所有与网站 通信时建立的 Session
。
下面我们来一起实现吧!
打开 .env
文件,我们将 SESSION_DRIVER
修改为 databse
,这样做的好处是可以沉淀每次与用户的会话做数据分析,用户地区分布,用户访问设备等客户端信息。最直白的作用就是可以通过这些用户的失效 session
分析用户登录地址,看看哪些用户在分享账号。
...
SESSION_DRIVER=database // 修改为 database
SESSION_LIFETIME=120
...
假设你已经配置好了数据库,我们既然要将 session
存储在数据库,那么还需要建立一个 sessions
表, Laravel 已经非常贴心的为我们准备好了。
执行命令:
php artisan session:table
Laravel 已经为我们生成了迁移文件,我们直接执行迁移命令来创建数据表就好
php artisan migrate
用户登录
下面我们来完成用户登录功能,执行命令来创建脚手架
php artisan ui:auth
访问 your-project-url/register
创建一个用户,并登录。
我们打开数据库中的 session
表
这条记录就是我们刚刚与网站建立的 Session
id user_id ip_address user_agent payload last_activity 服务端Session 关联的 user 表 ID 访问地址 终端设备 前端加密的SessionID 最后活跃时间
销毁之前的 Session
我们已经生成了用户脚手架,打开 app/Http/Controllers/Auth/LoginController.php
文件。
Laravel
的登录功能依赖于 AuthenticatesUsers
Trait。
// LoginController.php
class LoginController extends Controller
{
...
use AuthenticatesUsers; // 打开这个 trait
...
我们来打开这个文件看看,往下找到 authenticated
方法
/**
* The user has been authenticated.
*
* @param \Illuminate\Http\Request $request
* @param mixed $user
* @return mixed
*/
protected function authenticated(Request $request, $user)
{
//
}
这个方法的触发节点是 用户授权成功之后,写入 Session 之前,那我们可以通过它来销毁已登录用户之前的所有 Session
。
把它复制一下,然后回到 LoginController
文件,将 authenticated
方法覆写成我们自己的业务逻辑。
protected function authenticated(Request $request, $user)
{
DB::table('sessions')->where('user_id', $user->id)->delete()
}
我们可以简单粗暴的直接将用户之前登录 Session
删除,但是这样做并没有收集到用户信息,建立 Session
表就没意义了。
其实只需要将 session
表存储的 id
进行修改,使其与前端传输的 SessionID
不匹配,就代表会话失效,所以我们稍微修改一下
protected function authenticated(Request $request, $user)
{
DB::table('sessions')->where('user_id', $user->id)
->update([
'id' => DB::raw("concat('OUTMAN_', user_id, '_', id)"),
'user_id' => null,
]);
}
将 id
添加 OUTMAN
前缀,并拼接 user_id
字段,这样既达到了 Session 失效,又保留了用户信息。
你可能会疑问,既然我们只更新数据,那为什么不直接使用 软删除
呢?
其实 sessions
表不只存储已登录用户的 session
,在用户访问网站那一刻,就已经建立了 session
,如果用户没有登录,那么这条记录其实对我们来讲其实没有意义。
回到浏览器,打开一个 窗口A
,再使用快捷键 Ctrl+Shift+N
创建一个无痕 窗口B
,达到 Session 隔离的目的。
先在 窗口A
登录账号,再到 窗口B
登录相同的账号,然后再回到 窗口A
,按 F5 刷新一下页面,发现 窗口A
的用户已经退出。说明我们刚刚的代码生效了,因为在 窗口B
登录的时候,执行了查询,并把 窗口A
的 session 更新了。
我们再来看一下 sessions
表,已经有一个失效的 Session
了。
单用户登录功能已经实现,后面我们会给添加 websocket 通信,来实时通知用户。