如何管理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 ...
随机推荐
- mysql在cmd命令下执行数据库操作
windows+r 运行cmd命令,执行以下操作! 当mysql 数据库文件相对于来说比较大的时候,这个时候你可能在正常环境下的mysql中是导入不进去的,因为mysql数据库本身就有默认的导入文件大 ...
- Java 内存模型- Java Memory Model
多线程越来越多的使用,使得我们需要对它的深入理解.那么就涉及到了Java内存模型JMM.JMM是JVM的一部分,JMM定义了一个线程修改了一个共享变量,其他线程什么时候或者如何看到这个变量,如何去访问 ...
- 关于WSL(Windows上的Linux子系统)的介绍
WSL,Windows Subsystem for Linux,就是之前的Bash on [Ubuntu on] Windows(嗯,微软改名部KPI++ 首先要说一句,其实Windows 10在一周 ...
- HTTP 简要
HTTP协议就是客户端和服务器交互的一种通迅的格式. 当在浏览器中点击这个链接的时候,浏览器会向服务器发送一段文本,告诉服务器请求打开的是哪一个网页.服务器收到请求后,就返回一段文本给浏览器,浏览器会 ...
- 基于itchat的微信群聊小助手基础开发(一)
前段时间由于要管理微信群,基于itchat开发了一个简单的微信机器人 主要功能有: 图灵机器人功能 群聊昵称格式修改提示 消息防撤回功能 斗图功能 要开发一个基于itchat的最基本的聊天机器人,在g ...
- log4j 和slf4j的比较
log4j 和slf4j的比较 slf4j 官网:https://www.slf4j.org/manual.html slf4j(simple logging facade for java)是Jav ...
- C# AOP 面向切面编程之 调用拦截
有时候我们需要在代码中对方法调用进行拦截,并修改参数和返回值,这种操作叫做AOP(面向切面编程) 不过需要注意的是,AOP的效率很慢,在需要高效率场合慎用. 以下是C#的AOP方法: 首先建立一个控制 ...
- Ceph: A Scalable, High-Performance Distributed File System译文
原文地址:陈晓csdn博客 http://blog.csdn.net/juvxiao/article/details/39495037 论文概况 论文名称:Ceph: A Scalable, High ...
- 走进 Xamarin Test Recorder for Xamarin.Forms
此篇是承接之前 走进 UITest for Xamarin.Forms 的,所以如果没有看过之前的可以先看下之前的 UITest 比起上一篇纯敲代码只适合程序员的 UITest ,这一篇不管是程序员还 ...
- Vue源码后记-钩子函数
vue源码的马拉松跑完了,可以放松一下写点小东西,其实源码讲20节都讲不完,跳了好多地方. 本人技术有限,无法跟大神一样,模拟vue手把手搭建一个MVVM框架,然后再分析原理,只能以门外汉的姿态简单过 ...