remember me的问题
附录
知识要求有一定的WEB后端开发基础,熟悉Session的用法,以及与Redis、Database的配合
本文的原理讨论基于PHP的Laravel,尽管原理是通用的,但是读者具备相关知识理解会更轻松
背景公司在业务层面上,通常会期望自己运营的系统,一个注册帐号只能由本人使用或者少数几个人共用。实际情况却是:有些用户会将注册帐号的用户名和密码共享给成百上千个用户;也有的用户不直接提供用户名和密码,而是提供带有认证信息的cookie给其他用户,同样达到共享账号的目的。不论哪种形式,都造成了公司业务的损失。因此系统应该具备查看在线用户的功能,并可对在线用户实时管理,防止注册帐号被许多人共享。
技术原理在技术层面上,在线用户都是用Session表示。
用户通过用户名和密码正常登陆时,就会产生新的Session,退出则对应Session被清除。当多个用户使用同一账号登陆时,就会产生多个Session,防止共享账号数量太多就是要限制同一账号的Session数量。但是,这远远不够。
我们都知道,Session的原理是通过将session id写入cookie,下次浏览器访问时会把cookie带上来,识别其中的session id实现的(laravel存储session_id的cookie名称为laravel_session)。如果将该session id传递给其他用户,其他用户再将session id写入cookie,就可以达到共享账号,同时Session仍然是同一个的目的(见下图流程)。这种情况使得在线用户管理变得棘手,好在它们的IP并不相同,所以还是有办法处理。
上面两种形式是共享账号的两种主要形式,于是我们的问题就变成:
如何管理一个账号对应多个Session的问题
如何管理一个Session多个IP的问题
下面先对如何管理Session做综述,再对两种情况分开说明。
如何管理Session管理Session的前提是
系统能够获取到所有的Session
获取Session的所有IP信息。
一个高性能的系统,Session保存在缓存系统,比如Redis中。通过统一约定以session.开头的键为Session,就可以获取到所有Session,但这实际上是个很差的方法。Redis的设计并不是为了实现这样的目的,所以它的键值匹配要么效率极低,要么不能保证返回所有结果,同时它的扩展性非常地差,比如只读取某个用户的所有Session,需要对键的命名再做进一步约束。
于是我们换了另外一种方案,Session仍然保存在缓存系统中,同时异步保存在数据库中,注意必须是异步,否则会影响系统的运行。具体原理是,在Session的写入、销毁、回收这几个阶段发出event,将session连同HTTP请求放到队列中(队列是上下文无关的,获取不到任何HTTP请求的信息,需要从事件中读取),然后队列取出这些事件,写入到数据库。这样就能做到不影响性能,又可获取到所有的Session信息,并做灵活地管理。
Session默认没有携带IP信息,因此在每次Session写入时,需要再做一层加工,将IP写入Session,并且不能只保存一个IP,需要保存多个,以便后续问题的处理。
1.如何管理一个账号对应多个Session的问题既然数据库中已经保存了所有Session,在有新的Session产生时,检查是否超出指定数量。当超出时,自动删除最早的Session即可。
如何手动测试设置好要限制的数量,假设为2。安装SessionBox(Chrome插件),创建3个窗口以相同用户登陆,将发现最早登陆的窗口刷新后处于未登陆状态。
合理的Session数量一个用户可能从多个设备登陆,比如PC、手机、平板,所以Session数量至少在3个以上。用户也可能在PC上开N个不同浏览器,导致同一个设备有多个Session,应该优化此种情况,判定为同一个设备。具体看《TODO》这一节说明。
2.如何管理一个Session多个IP的问题所有的用户共享同一个Session,也就没办法精确控制要保留的数量了。要么删除Session,所有用户重新登陆;要么重新生成session id,只保留一个用户,其他所有用户需要重新登陆。目前采用后者,因为前者有一个风险,同一个用户可能从多个地方登陆产生了大量的IP,结果就踢出去了,用户体验不好。而如果是后者,如果一个用户,只是自己使用,那么不论他的ip数量是否突破限制,重新生成session id仍然是他的,所以不会受影响。
它的原理是用户发起请求,发现IP过多,就重新生成session id,这个新的session id会写到该用户的cookie中,而其他用户由于没有这个新的session id,所以需要重新登陆。