精品 客户端session vs 服务端session
 发布于 11 年前  作者 DoubleSpout  21567 次预览  最后一次回复是 11 年前  来自  

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 回复
dead-horse

koa 有 cookieSession 的中间件

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

tulayang

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

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

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

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

DoubleSpout

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

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

DoubleSpout

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

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

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

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

dead-horse

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

DoubleSpout

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

youxiachai

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

youxiachai

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

DoubleSpout

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

DoubleSpout

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

tulayang

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

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

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

youxiachai

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

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

DoubleSpout

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

tulayang

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

chemdemo

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

DoubleSpout

只是个概念罢了~

DoubleSpout

貌似争论的很激烈

DoubleSpout

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

fengmk2

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

whatsmynick

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

DoubleSpout

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

DoubleSpout

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

hainee

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

hainee

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

alsotang

我在 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 也一起放进缓存中。

DoubleSpout

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

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

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

DoubleSpout

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

lyjyiran

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

DoubleSpout

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