精华 客户端session vs 服务端session
发布于 10 年前 作者 DoubleSpout 19918 次浏览 最后一次编辑是 8 年前

session想来大家都不会陌生,session主要的作用就是用来记录用户状态,表明用户身份的,session的存储方式也有多样,最为传统的就是服务端保存session的内容,客户端浏览器cookie保存sessionid,服务端通过客户端每次http请求带上的cookie中的sessionid去找到对应此用户的session内容。当然我之前也发过一篇文章讲到过通过etag来做为sessionid,识别用户身份。相关文章链接:http://cnodejs.org/topic/5212d82d0a746c580b43d948

最近在做公司一个项目,我们用到了python的flask框架,一个轻量级,易上手的python web框架,有点类似nodejs中的expressjs,一些周边相关的模块集成的比expressjs好一些,题外话,写了阵python回来写node,代码那是各种整齐啊。 说重点,flask和我之前用过的其他框架有一点不同的是,它的session默认是完全保留在客户端浏览器中的,也就是说我往flask的session中写入数据,最终这些数据将会以json字符串的形式,经过base64编码写入到用户浏览器的cookie里,也就是说无须依赖第三方数据库保存session数据,也无需依赖文件来保存,这一点倒是挺有意思。

估计很多人要问我安全性问题了,session内容保存在客户端cookie,如何保证它的保密性、完整性和容量限制,保密性表示不被其他用户窥探,完整性表示不被恶意用户篡改,容量限制则表示cookie中必然无法保存过多的数据。

1、保密性,flask 这方面可以说是完全不设防,经过简单的base64编码,很容易就可以对它进行decode,然后看到其中的数据,不过话又说回来,session中本来记录的就是当前用户的一些相关身份信息和操作记录,就算被本用户看见又何妨,如果怕网络传输时被截获,那用户其他的一些 http 请求操作也照样被截获,无关乎客户端的cookie session设计

2、完整性,flask 利用开发者定义的一个私钥对数据进行简单的hash签名认证,所以我们会看到在 flask 保存在浏览器cookie中的session值,经过base64解码之后都是这样的:

{"userid":12345}.xxxxxxxxxxxxx

json字符串后面的xxxx就是利用开发人员定义的密钥对前面json字符串进行hash生成的签名,所以数据的完整性只要保证一个足够强大的密钥不被泄露就行了。

3、容量限制,这点确实是cookie session的不足,因为客户端cookie是有容量限制的,不可能像使用第三方数据库保存session那样存放大量的数据,不过一般我们开发一些web站点,session在大部分情况下主要是用来表示用户身份,比如一个 userid,一个头像地址,或者一个nickname等,真正用户的一些其他信息还是要通过userid去数据库获取的。

所以在开发一些对session保存容量要求不大,但是并发量比较大,而且需要多机器,多进程服务的项目来说,客户端session无疑是一个即轻松又节约设备的方法,至少节约了1台redis服务器。

我在开发完这个 flask 项目后,想到了node也应该在npm上有这样一个包,可惜我搜了下npmjs.org,似乎没有找到特别雷同的cookie session store的包,于是本着重复造轮的思想,写了一个让nodejs也支持客户端 cookie session保存的package,client-session,项目地址:

https://github.com/DoubleSpout/nodeClientSession

安装方法:

npm install client-session

client-session包主要解决了nodejs多进程下内存session无法同步的问题,让吊丝们的1G内存 vps 也能够不开redis,多进程同步session数据;同时在flask仅利用base64将session内容编码的基础上,做了一个小的加解密方法,让数据看上去更加安全一些,仅是安全一些,还是尽量不要保存用户密码,银行账户信息在里面哦。

client-session对数据加解密,base64编码以及签名和签名验证都是通过c++插件完成,所以相对于纯js性能上可能有那么点优势(没做过测试,不能妄下定论啊),另外在我的压力测试中,利用expressjs框架,多进程使用client-session要比使用本机开启redis数据库存session,性能高出20%左右,测试数据详见项目地址,client-session使用方法也很简单,一个结合expressjs框架返回计数器的代码例子:

 var express = require('express');
var path = require('path')
var cs = require('client-session');
var clientSession = cs('mysecretkey');
var app = express();
app.use(clientSession.connect()); //调用client-session中间件
app.get('/', function(req, res){
  var count = req.csession['count'];
  if(!count) count = 1;
  else count++;
  req.csession['count'] = count;
  //这边不想暴力的改掉express框架的res.send方法
 //就劳烦开发人员手动调用一下csflash,毕竟1台redis服务器是省下了
  req.csflush(); 
  res.send(count.toString());
});
app.listen(8124);
console.log('Server running at http://127.0.0.1:8124/');

利用ab命令:

ab -c 500 -n 20000 http://192.168.150.3:8124/

压力测试内网的一台2cpu 6Gmen linux云主机

express + redis + 2 process session::
Requests per second:    380.54 [#/sec] (mean)

express + client-session +2 process session:
Requests per second:    492.25 [#/sec] (mean)

具体的client-session使用方法,详见项目github地址,有啥问题可以留言给我。

对于客户端session和服务端session的优劣特点,大家都明白了,最终如何选择还是看项目需求吧~

另外expressjs似乎没有在响应前触发的事件吧,比如:

res.on('beforeResponse', function(){
    //do something
})
31 回复

koa 有 cookieSession 的中间件

cookieSession 性能肯定要更高,不过当 session 里需要存放的东西变多的时候就不是那么适合了

宝贝,别闹了,数据不会无缘无故的从空气中跳出来。 不管你用什么框架,总要有一个地方去存储数据,要么是磁盘要么是内存。

客户端发来cookie,你要验证cookie的正确性,就必须从服务器取出sessionid, 这个sessionid要么是在内存中,要么是在文件系统,要么是在数据库文件中。 但是归根结底,你都需要一个磁盘来放置sessionid。[文本文件或者数据库文件] 每次启动服务器,从此磁盘读取sessionid缓存到服务器上而已。

文本文件放sessionid就存在一个安全性问题,因为数据库服务器一般会防火墙限制IP,但是nodejs服务器可是对所有人开放的,极容易被攻击。

使用nodejs内核2进制编译过的crypto,你就能得到标准安全可靠单向的40位sha1加密,32位md5加密,而且只需要几行代码。

擦,又重复造了一个轮子,不过简单看了下 tj 大神的代码,似乎他对cookie session也只是做了一个base64编码和解码,koa cookie session这个模块对开发人员定义的session secret key没有做任何签名生成和签名认证,可能在框架其他地方做掉了把,我这个 client-session 好坏也对cookie里的值做了简单的加密,而且通用性好一些,可以在没有框架的地方使用,也算重复造了一个不一样的轮子把。

空间限制确实是cookie seesion的弊端,所以这个还是要根据自己的项目的实际需求选择server session和cookie session了,其实像我们cnode这种简单社区类的,完全可以用cookie session来存放一些用户的未读消息数,用户id等等数据。

你说的客户端通过sha1或md5生成sessionid存在浏览器cookie中,服务器根据sessionid获取session数据是比较传统的做法,你可以仔细看下我们的这篇文章,它所说的是另外一种session数据的存放方式,将整个session数据保存在用户浏览器的cookie里,通过签名和加密保证session数据的保密性和完整性,因为使用文件存储session数据会碰到多机器无法共享session的问题,最后只能通过前端代理服务器根据用户 ip 进行哈希将请求转发到指定服务器;而通过数据库保存session,由于用户每次请求都会产生一次对数据库的读写,所以数据库那边压力会比较大。

而使用cookie session恰恰适合对session空间存储不大的项目,同时cookie session也很好解决了上面2个问题。

”文本文件放sessionid就存在一个安全性问题,因为数据库服务器一般会防火墙限制IP,但是nodejs服务器可是对所有人开放的,极容易被攻击。“

这句话,我不是很明白你的意思

@snoopy 是的,koa框架的 cookie 就是已经加密的

@dead-horse 是base64编码的还是加密?我看他源码只是做了一层base64编码,你可以试试将他的cookie数据base64 decode看看,是不是能看到json数据~

connect/express 一直都有这个东西啊… https://github.com/expressjs/cookie-session

关于加密那块 用的都是做验证 https://github.com/expressjs/keygrip

好吧,我又造了一个轮子。。。

这个包不错,用来做简单的验证,省的自己重复写了

@snoopy 数据库服务器 <-> nodejs服务器 <-> 代理

数据库服务器一般只允许nodejs服务器层指定IP访问, nodejs服务器则是接到互联网不限IP。

如果sessionid直接保存到nodejs服务器,任何人都有机会获取你所有的sessionid。

关于这个更多讨论,可以去看一下ruby china …

https://ruby-china.org/topics/19520

@tulayang 怎么会呢?sessionid如果保存在nodejs进程的一个对象里,别人也是获取不到的啊~

@snoopy 这么说吧。 有人加入了你的网站,你要保存这个人的sessionid, 肯定要在磁盘上做个备份的。 只保存在内存? 你的服务器宕下机, 重启一下, 你让对方重新注册一下??? 那么保存在磁盘上, 保存在你的nodejs服务器, 一旦你的nodejs服务器被攻击, 你的sessionid就全给别人了。

顶一下 之前也简单这么干过 把用户信息md5加密之后赛到cookie 在server端简单解密 不过那时候还不知道这就是client-session 嘿嘿

只是个概念罢了~

貌似争论的很激烈

@tulayang nodejs被攻击是指什么样子的攻击?是被黑客拿到root?如果真被黑客拿到root了,那么无论数据存哪里,什么都交待给他了。除了服务器权限直接被黑客拿到,其他攻击我想是无法拿到你保存在磁盘里的sessionid的吧~

cookiestore最大的问题除了安全,就是传输cookie header的大小分分钟比响应body还要大。。。 也只能在session里面放很基本的信息,隐私信息完全不能放在里面。不过接手开发的同学未必知道这些安全问题。

session本来就不用来放大数据的, 如果出现session过大, 那是业务设计的问题。 至于说cookiestore存在莫须有的安全问题的, 劳架curl -I一下twitter和github, 看看cookie里的_xx_sess部分, 再顺手把他们黑了佐证一下自己的论点。

苏神一言中要害啊,根据实际需要自己选择呗,这些都要跟接手开发的同事事先说明好的~

同意你的观点 +1,那些认为cookiesession不安全的朋友不要臆想,可以试试自己实际攻击一次,拿到cookiesession再说cookiesession不安全

@snoopy 衣服都被拔光了,两只手能当得了多少呢?

@snoopy 对滴,根据需求来使用,很多开发的新同学完全没安全意识,经常写一些把我吓尿的危险代码,比如在客户端用JS拼Sql语句:(

我在 https://github.com/alsotang/node-lessons/tree/master/lesson16 中写了这么一段:

不过 cookie-session 我个人建议不要使用,有受到回放攻击的危险。

回放攻击指的是,比如一个用户,它现在有 100 积分,积分存在 session 中,session 保存在 cookie 中。他先复制下现在的这段 cookie,然后去发个帖子,扣掉了 20 积分,于是他就只有 80 积分了。而他现在可以将之前复制下的那段 cookie 再粘贴回去浏览器中,于是服务器在一些场景下会认为他又有了 100 积分。

如果避免这种攻击呢?这就需要引入一个第三方的手段来验证 cookie session,而验证所需的信息,一定不能存在 cookie 中。这么一来,避免了这种攻击后,使用 cookie session 的好处就荡然无存了。如果为了避免攻击而引入了缓存使用的话,那不如把 cookie session 也一起放进缓存中。

@alsotang 说的有道理啊,所以cookie session只适合放一些比如用户id,用户昵称,头像之类的东西。

像你提到的积分问题,只能通过用户id去库里查了再显示给用户了,所以cookie session在提供便利的同时,在程序设计和编码时也要多一个心眼,谨慎一些,以防被恶意利用了。

验证cookie session一般也就是通过签名,或者 3des 加密,可以保证cookie session不会被恶意篡改,但是你提到的回放攻击是没有办法的。

@alsotang 随便说了几句,我推荐nodeclub使用cookie session~

@alsotang 积分放session的业务设计本身存在缺陷, 通俗的服务端session并不能保障原子性, 就算用户有100积分, 同时并发性的发起5个请求, 每个请求扣20分, 到最后全部执行完写入session可能是0分/20分/40分/60/80分, 并不能保证一定是0分.

@lyjyiran 他只是举个例子,现实当然不可能把积分放session里,不然用户删了cookie积分难道就清0了。这里只是举例回放cookie攻击的可能性

回到顶部