目录

  • 知识要求
  • 背景
  • 技术原理
  • 如何管理Session
  • remember me的问题
  • 附录

知识要求

  • 有一定的WEB后端开发基础,熟悉Session的用法,以及与RedisDatabase的配合
  • 本文的原理讨论基于PHPLaravel,尽管原理是通用的,但是读者具备相关知识理解会更轻松

背景

公司在业务层面上,通常会期望自己运营的系统,一个注册帐号只能由本人使用或者少数几个人共用。实际情况却是:有些用户会将注册帐号的用户名和密码共享给成百上千个用户;也有的用户不直接提供用户名和密码,而是提供带有认证信息的cookie给其他用户,同样达到共享账号的目的。不论哪种形式,都造成了公司业务的损失。因此系统应该具备查看在线用户的功能,并可对在线用户实时管理,防止注册帐号被许多人共享。

技术原理

在技术层面上,在线用户都是用Session表示。

用户通过用户名和密码正常登陆时,就会产生新的Session,退出则对应Session被清除。当多个用户使用同一账号登陆时,就会产生多个Session,防止共享账号数量太多就是要限制同一账号的Session数量。但是,这远远不够。

我们都知道,Session的原理是通过将session id写入cookie,下次浏览器访问时会把cookie带上来,识别其中的session id实现的(laravel存储session_idcookie名称为laravel_session)。如果将该session id传递给其他用户,其他用户再将session id写入cookie,就可以达到共享账号,同时Session仍然是同一个的目的(见下图流程)。这种情况使得在线用户管理变得棘手,好在它们的IP并不相同,所以还是有办法处理。

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

  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。安装SessionBoxChrome插件),创建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个请求,都发现sessionip过多,于是删除旧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电脑,将发现处于未登陆状态。

合理的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(见流程图)。

在了解上述机制后,即可发现,当remember me的用户超过session限制数量后,最早的session被删除,但由于该用户有remember me,所以会重新生成session自动恢复,也就说,删除sessionremember me用户无效,会立刻重新生成。所以上述的session管理方案不应该开启remember me,否则是有问题的。

那为什么不直接Remember Me的方案呢?主要原因在于它的设计比较复杂,最终我们会切换成Remember Me的机制,将会另开一篇专门讨论。

附录

知识点

Q:Session与登陆用户的关系?

A:严格来说,只要用户打开浏览器访问网站,就会产生Session标识一个会话,跟是否登陆无关。但是一般情况下,我们只关心登陆用户的Session,因此这里讨论上不做区分,只要产生Session就认为有登陆用户。

参考

如何管理Session(防止恶意共享账号)——理论篇的更多相关文章

  1. 2537-springsecurity系列--关于session的管理2-session缓存和共享

    版本信息 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring ...

  2. 如何实现session跨服务器共享

    Session共享有多种解决方法,常用的有四种:客户端Cookie保存.服务器间Session同步.使用集群管理Session.把Session持久化到数据库. 1.客户端Cookie保存 以cook ...

  3. 170222、使用Spring Session和Redis解决分布式Session跨域共享问题

    使用Spring Session和Redis解决分布式Session跨域共享问题 原创 2017-02-27 徐刘根 Java后端技术 前言 对于分布式使用Nginx+Tomcat实现负载均衡,最常用 ...

  4. 【Tomcat】Tomcat Session在Redis共享

    参考的优秀文章 Redis-backed non-sticky session store for Apache Tomcat 简单地配置Tomcat Session在Redis共享 我使用的是现有的 ...

  5. 【Hibernate】Hibernate系列8之管理session

    管理session 更简单的,注入对象:

  6. :Hibernate逍遥游记-第16管理session和实现对话

    1. package mypack; public class Monkey{ private Long id; private String name; private int count; pri ...

  7. Hibernate管理Session和批量操作

    Hibernate管理Session Hibernate自身提供了三种管理Session对象的方法 Session对象的生命周期与本地线程绑定 Session对象的生命周期与JTA事务绑定 Hiber ...

  8. Hibernate之管理session与批处理

    1. Hibernate 自身提供了三种管理Session对象的方法 –Session对象的生命周期与本地线程绑定 –Session 对象的生命周期与JTA事务绑定 –Hibernate 委托程序管理 ...

  9. session跨域共享解决方案

    要让session跨域共享,需要解决三个问题: 1.通过什么方法来传递session_id? 2.通过什么方法来保存session信息? 3.通过什么方法来进行跨域? 一.传递session_id有4 ...

随机推荐

  1. iOS音频格式PCM转G711u(或G711a-law)

    请尊重作者劳动成果,如需转载本博客文章请注明出处!谢谢合作!  inputData是PCM的实时数据,可以通过转码,获取到最后导出的G711u数据(sendData) NSUInteger datal ...

  2. 【原创】自用css reset

    自己工作中常用的reset,和一些设置,实际用时会根据网站页面进行增删. /* Common style */html{ overflow-y:scroll; overflow-x:auto;}bod ...

  3. 如何从MVP模式进阶到Clean模式

    从类图上来看,MVP都是一个业务一个Presenter,每个Presenter都是一个接口,它还包含了View的接口,用于定于和View相关的行为,然后Activity等业务类实现View的接口,因为 ...

  4. web自动化测试从入门到持续集成(selenium webdriver)

    在很多刚学习自动化的可能会认为我只需要会运用selenium,我只需要在一个编辑器中实用selenium +java编写了一些脚本那么就会自动化了,是真的吗?答案肯定是假的.自动化肯定是需要做到真的完 ...

  5. django中使用sha1,md5加密

    # salt 盐 使用sha1加密算法,返回str加密后的字符串 # 提高字符串的复杂的 from hashlib import sha1 def get_hash(str, salt=None): ...

  6. windows消息简单应用实例

    //基本定义 internal class MyMessager : IMessageFilter { public bool PreFilterMessage(ref Message m) { // ...

  7. Java基础总结--常用类以及包的访问权限

    -----Object---所有类的根类1.怎么得到的:通过对所有对象不断的向上抽取共性,具备所有对象的共性的东西2.常用的方法* equals(Object obj):比较两个对象的引用是否指向同一 ...

  8. C#连接oracle数据库提示ORA-12154: TNS: 无法解析指定的连接标识符

    C#连接oracle数据库提示ORA-12154: TNS: 无法解析指定的连接标识符如果PLSQL Develope能连接上而用代码无法连接上则可以考虑sqlnet.ora文件中是否有NAMES.D ...

  9. asp.net 第三方UI控件 Telerik KendoUI 之 TreeVIew 的用法记录

    一.前台显示 备注:一次性取出所有节点 function loadTreeData() { $.ajax({ type: 'POST', url: '@(Html.UrlHref("Scri ...

  10. SE6 模板字符串详解

    SE6引入了模板字符串这样一个概念,让我们从无止尽的+连接字符串中解脱了出来,SE5中也可以在字符串末尾添加\实现,不过模板字符串更加好用和强大. SE6模板字符串是用反撇号(`,即键盘上和~键同一个 ...