天天看点

如何管理Session(防止恶意共享账号)——理论篇

目录

  • 知识要求
  • 背景
  • 技术原理
  • 如何管理

    Session

  • remember me

    的问题
  • TODO

  • 附录

  • 有一定的

    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(防止恶意共享账号)——理论篇

上面两种形式是共享账号的两种主要形式,于是我们的问题就变成:

  1. 如何管理一个账号对应多个

    Session

  2. 如何管理一个

    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

,所以需要重新登陆。

从这个流程中也可看出,这个处理过程必须是在用户发起请求时处理。需要注意的是,这个过程会需要考虑并发,即便是单个用户访问。假设一个用户访问页面,该页面同时发起

4

个请求。服务端同时处理这

4

个请求,都发现

session

ip

过多,于是删除旧

session

重新生成,造成一个问题:

4

个请求删除同一个旧

session

,然后生成了

4

个不同的

session

。为防止这种情况,使用了

Redis

对该

session id

加锁,并设置

30

秒自动过期,只有第一个获取锁的人执行重新生成,其他没获得锁的请求不处理。然后锁不用释放,自然过期即可。

正常而言,

session

已经被重新生成了,旧

session id

是走不到加锁

session id

这一步的。如果有,那一定是在重新生成之前就进来的请求,而这些请求本来就应该被忽略。反之,如果删除锁,这些请求将再次加锁并重新生成

session

,仍然会造成刚才说的问题,因此直接让锁自动过期即可。

假设

IP

数量限制为1个,打开

A``B

两台电脑,在

A

电脑上先登陆,打开

Chrome开发者工具

,复制

laraval_session

的值;然后传到

B

电脑,打开

Chrome开发者工具

,设置

laravel_session

的值,然后刷新下将发现变为登陆状态。再刷新下

A

电脑,将发现处于未登陆状态。

如何管理Session(防止恶意共享账号)——理论篇

ip

限制数量

这个值则很主观,同时多个用户共享一个

Session

的问题实际是可避免的,具体参考《

Remember Me

的问题》的说明。因此不建议设置得太小,建议在

5

以上。

remember me的问题

如果一个账号在线的存活期只有几个小时,那么上面说的问题影响范围都有限。为提高用户体验,用户一次登陆后可存活好几天甚至永久存活。提高存活时间有两种方法:

  1. 修改

    Session

    的过期时间
  2. 使用

    Remember Me

    的机制

上面管理

Session

的方案,在第

1

种方法下能顺利工作,但是在第

2

种方法下则没法工作。所以使用了

Remember Me

的方案,在管理机制上需要重新设计。

要理解这个问题所在,我们需要理解

Remember Me

的机制:用户登陆后,如果设置了

Remember Me

,服务器会生成

remember me token

,保存在数据库中,并将该

token

写入到

cookie

中。用户

Session

过期后,再次访问浏览器,服务端发现

cookie

中的

remember me

信息与数据库中的一致,就重新生成新的

Session

(见流程图)。

如何管理Session(防止恶意共享账号)——理论篇

在了解上述机制后,即可发现,当

remember me

的用户超过

session

限制数量后,最早的

session

被删除,但由于该用户有

remember me

,所以会重新生成

session

自动恢复,也就说,删除

session

remember me

用户无效,会立刻重新生成。所以上述的

session

管理方案不应该开启

remember me

,否则是有问题的。

那为什么不直接

Remember Me

的方案呢?主要原因在于它的设计比较复杂,最终我们会切换成

Remember Me

的机制,将会另开一篇专门讨论。

TODO

  • [ ] 多个

    Session

    可能是同一个设备发出的,因此应该结合

    IP

    判断是否是同一个设备,或者结合客户端发出的唯一标识(唯一标识需要是PC的唯一标识)。如果是同一个设备的就都放过的话,其实也有问题,

    SessionBox

    这样的工具可以在单个浏览器创建多个

    Session

    ,如果这个过程脚本化,服务器会产生大量垃圾

    Session

    ,所以也应该限制数量。
  • [ ] 改为

    Remember me

    方案,将问题简化为只考虑一个帐户多个

    Session

    的问题。
  • [ ] 客户端发送唯一标识的方式是否具备可行性?

知识点

Q:

Session

与登陆用户的关系?

A:严格来说,只要用户打开浏览器访问网站,就会产生

Session

标识一个会话,跟是否登陆无关。但是一般情况下,我们只关心登陆用户的

Session

,因此这里讨论上不做区分,只要产生

Session

就认为有登陆用户。

参考

  • Chrome插件:SessionBox
  • redis加锁性能问题