如何管理Session(防止恶意共享账号)——理论篇
目录
- 知识要求
- 背景
- 技术原理
- 如何管理
Session 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,所以需要重新登陆。
从这个流程中也可看出,这个处理过程必须是在用户发起请求时处理。需要注意的是,这个过程会需要考虑并发,即便是单个用户访问。假设一个用户访问页面,该页面同时发起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电脑,将发现处于未登陆状态。

合理的ip限制数量
这个值则很主观,同时多个用户共享一个Session的问题实际是可避免的,具体参考《Remember Me的问题》的说明。因此不建议设置得太小,建议在5以上。
remember me的问题
如果一个账号在线的存活期只有几个小时,那么上面说的问题影响范围都有限。为提高用户体验,用户一次登陆后可存活好几天甚至永久存活。提高存活时间有两种方法:
- 修改
Session的过期时间 - 使用
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自动恢复,也就说,删除session对remember me用户无效,会立刻重新生成。所以上述的session管理方案不应该开启remember me,否则是有问题的。
那为什么不直接Remember Me的方案呢?主要原因在于它的设计比较复杂,最终我们会切换成Remember Me的机制,将会另开一篇专门讨论。
附录
知识点
Q:Session与登陆用户的关系?
A:严格来说,只要用户打开浏览器访问网站,就会产生Session标识一个会话,跟是否登陆无关。但是一般情况下,我们只关心登陆用户的Session,因此这里讨论上不做区分,只要产生Session就认为有登陆用户。
参考
如何管理Session(防止恶意共享账号)——理论篇的更多相关文章
- 2537-springsecurity系列--关于session的管理2-session缓存和共享
版本信息 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring ...
- 如何实现session跨服务器共享
Session共享有多种解决方法,常用的有四种:客户端Cookie保存.服务器间Session同步.使用集群管理Session.把Session持久化到数据库. 1.客户端Cookie保存 以cook ...
- 170222、使用Spring Session和Redis解决分布式Session跨域共享问题
使用Spring Session和Redis解决分布式Session跨域共享问题 原创 2017-02-27 徐刘根 Java后端技术 前言 对于分布式使用Nginx+Tomcat实现负载均衡,最常用 ...
- 【Tomcat】Tomcat Session在Redis共享
参考的优秀文章 Redis-backed non-sticky session store for Apache Tomcat 简单地配置Tomcat Session在Redis共享 我使用的是现有的 ...
- 【Hibernate】Hibernate系列8之管理session
管理session 更简单的,注入对象:
- :Hibernate逍遥游记-第16管理session和实现对话
1. package mypack; public class Monkey{ private Long id; private String name; private int count; pri ...
- Hibernate管理Session和批量操作
Hibernate管理Session Hibernate自身提供了三种管理Session对象的方法 Session对象的生命周期与本地线程绑定 Session对象的生命周期与JTA事务绑定 Hiber ...
- Hibernate之管理session与批处理
1. Hibernate 自身提供了三种管理Session对象的方法 –Session对象的生命周期与本地线程绑定 –Session 对象的生命周期与JTA事务绑定 –Hibernate 委托程序管理 ...
- session跨域共享解决方案
要让session跨域共享,需要解决三个问题: 1.通过什么方法来传递session_id? 2.通过什么方法来保存session信息? 3.通过什么方法来进行跨域? 一.传递session_id有4 ...
随机推荐
- 小白学Maven第一篇配置
在百度上搜Maven进入官网,然后在进Download里面把apache-maven-3.5.0-bin.zip(记得不要下错)下载下来 然后进行安装 (前提你配置了Java如下图) Java配置: ...
- EasyUI DataGrid 基于 Ajax 自定义取值(loadData)
为 datagrid 加载数据分两种情况: 一种是基于 Ajax 请求获取数据然后通过"loadData"方法来赋值: 另一种是直接使用 datagrid 自带的"loa ...
- 使用wget做站点镜像及wget的高级用法
本文为大家介绍 使用wget做站点镜像及wget的高级用法,供大家学习参考. # wget -r -p -np -k http://xxx.edu.cn -r 表示递归下载,会下载所有的链接,不过要注 ...
- BZOJ-3040-最短路(road)
Description N个点,M条边的有向图,求点1到点N的最短路(保证存在).1<=N<=1000000,1<=M<=10000000 Input 第一行两个整数N.M,表 ...
- LINUX 笔记-ubuntu 配置 jdk 环境
在 /etc/profile 文件尾添加 JAVA_HOME=/opt/jdk1.8.0JRE_HOME=${JAVA_HOME}/jre PATH=$PATH:$HOME/bin:$JAVA_H ...
- js正则验证特殊字符
js正则验证特殊字符 方案一 var regEn = /[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/im, regCn = /[·!#¥(--):: ...
- Python实现正交实验法自动设计测试用例
1.简介 正交试验法是研究多因素.多水平的一种试验法,它是利用正交表来对试验进行设计,通过少数的试验替代全面试验,根据正交表的正交性从全面试验中挑选适量的.有代表性的点进行试验,这些有代表性的点具备了 ...
- 优先队列(存储结构数组)--Java实现
/*优先队列--是对队列的一种改进 *要存储的数据存在优先级--数值小的优先级高--在队头 *优先队列的实现 *1.数组:适合数据量小的情况(没有用rear+front实现) *优先队列头在items ...
- eval函数的坑
开发工作中遇到这样一种情形,需要执行用户输入的php代码串,于是决定使用eval函数.coding大概示例如下: function getStr($str) { return strlen($str) ...
- ssm框架的整合
首先创建一个web工程,我这里使用的IDE为eclipse. 结果目录如下: 添加相关的jar包: 接下来是完成配置文件 首先我们先配置web.xml: <?xml version=" ...