如何管理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 ...
随机推荐
- sbt 配置文件
配置文件名:application.conf, VM options: -Dconfig.file=application.conf 配置文件内容规范: openStrategy{ alive { a ...
- Python通过future处理并发
future初识 通过下面脚本来对future进行一个初步了解:例子1:普通通过循环的方式 import os import time import sys import requests POP20 ...
- riot.js教程【二】组件撰写准则、预处理器、标签样式和装配方法
基本要求 一个riot标签,就是展现和逻辑的组合(也就是html和JS): 以下是编写riot标签最基本的规则: 先撰写HTML,再撰写JS,JS代码可以写在<script>标签内部,但这 ...
- 对象反序列化时,抛出java.io.StreamCorruptedException: invalid type code: AC异常
问题描述:在使用java.io.ObjectInputStream类的readObject()方法去读取包含有序列化了多个(两个及两个以上)类的文件时,当读取到第二个类时,会抛出题目中提到的异常. 原 ...
- mysql执行sql脚本
最近用mysql执行sql脚本,遇到一些问题,顺便记录一下笔记. 首先,先开启mysql服务,创建一个空数据库(脚本里没有创建数据库) 执行脚本有两个方法 1.未连接数据库:在Windows下使用cm ...
- USACO奶牛博览会(DP)
Description 奶牛想证明他们是聪明而风趣的.为此,贝西筹备了一个奶牛博览会,她已经对N头奶牛进行了面试,确定了每头奶牛的智商和情商. 贝西有权选择让哪些奶牛参加展览.由于负的智商或情商会造成 ...
- Java基础总结--常用类以及包的访问权限
-----Object---所有类的根类1.怎么得到的:通过对所有对象不断的向上抽取共性,具备所有对象的共性的东西2.常用的方法* equals(Object obj):比较两个对象的引用是否指向同一 ...
- vuex的简易入门
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px "Helvetica Neue"; color: #454545 } p. ...
- ASP.NET Core中的OWASP Top 10 十大风险-SQL注入
不定时更新翻译系列,此系列更新毫无时间规律,文笔菜翻译菜求各位看官老爷们轻喷,如觉得我翻译有问题请挪步原博客地址 本博文翻译自: https://dotnetcoretutorials.com/201 ...
- 如何线上部署node.js项目
来源:http://blog.csdn.net/chenlinIT/article/details/73343793 前言 最近工作不是很忙,在空闲时间学习用node+express搭建自己的个人博客 ...