cookie与session,双刃剑的两面,多捅几次就明白了。
一、Cookie
1. 定义
储存在用户本地客户端上的数据,由键值对组成,意义由服务端决定,通常用于标识用户身份,session跟踪等。客户端与服务端建立连接时,cookie会被自动发送到客户端。
2. 设置cookie
服务端设置cookie是通过响应的Set-Cookie
字段,格式如下:
Set-Cookie: name=value; Path=/; Expires=time1; Max-Age=time2; Domain=.domain.com;Secure; HttpOnly
- name:cookie的键名;
- value:cookie的键值,键和值是必须的选项;
- Path:标识这个cookie影响到的路径,当前访问路径不满足该匹配时,浏览器不发送这个cookie。设置为”/“表示根路径,子目录可以访问父目录的cookie,所以”/“意味着所有子路径都可以访问它;
- Expires和Max-Age:表示cookie的存活时间,缺省时cookie会随着窗口关系而清除;
- HttpOnly:告知浏览器不允许通过脚本document.cookie去更改这个cookie值(因为不可见);
- Secure:设置了此选项(不用赋值)时表示cookie只在HTTPS协议下传送;
下面实现一个组装cookie的小函数:1
2
3
4
5
6
7
8
9
10var serialize=function(name,val,opt){
var pairs=[name+'='+encode(val)]; //存储cookie的数组
opt=opt||{};
if(opt.maxAge) pairs.push('Max-Age='+opt.maxAge);
if(opt.domain) pairs.push('Domain='+opt.domain);
······
return pairs.join(';');
};
3. cookie优化
- 减小cookie大小;
- 静态组件使用不同域名:静态组件往往不需要cookie,设置不同域名还可以加速客户端加载资源;
二、Session
1. 定义
Http协议是无状态协议,原则上服务端无法区分本次请求和上次请求之间的关系,但是实际中我们可能需要服务器识别并记忆用户的身份,以避免频繁的要求用户输入密码来确认身份,比如你在淘宝的登陆页面登陆了,那么你在商品页面的操作就不需要再次输入身份信息。这样的身份标识可以用cookie来表示,但是cookie可以被前后端修改,用户身份容易被伪造,所以Session应运而生。
Session是存储在服务端,用于标识会话信息的数据。Session不会被客户端修改,安全性更高。
2. 基于cookie实现
利用cookie实现session的核心是:在cookie中存储一个口令(token),这个口令和服务端是一一对应的,如果被篡改就失去了口令的意义。通常服务端的session是有时间限制的,可能过半小时服务端的口令就会被销毁,比如你登陆了淘宝的页面,然后你去吃饭了,一个小时后发现淘宝会提示你重新登陆。
整个过程大致为:
客户端请求->服务端为客户端建立session->客户端收到口令并存储->客户端再次请求时发送这个token->服务端验证token,如果验证通过则授予特殊操作权限。
以下是生成session的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14var sessions={}; //存储session的对象
var key="sid"; //存储在客户端的cookie的键名
var EXPIRES=20*60*1000; //过期时间
var generate=function(){
var session={}; //一个单独的session同样用对象来表示
session.id=(new Date()).getTime()+Math.random(); //每一个会话的sessionID用时间+随机值来表示
session.cookie={ //设置cookie的过期时间
expire:(new Date()).getTime()+EXPIRES
};
sessions[session.id]=session; //存储session
return session;
};
以下是验证session的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function(req,res){
var id=req.cookies[key]; //获取sid,这里的cookies数组是原生的请求头解析后的结果,key就是"sid"
if(!id){ //id不存在,说明客户端cookie过期删除了,没有发送到服务端
req.session=generate(); //重新设置session
}else{
var session=sessions[id]; //获取作为比对的服务端令牌
if(session.cookie.expire > (new Date()).getTime()){ //没有过期
session.cookie.expire = (new Date()).getTime()+EXPIRES; //更新过期时间
req.session=session;
}else{ //过期了,销毁并重新生成
delete sessions[id];
req.session=generate();
}
}
handle(req,res); //继续处理
}
这里需要说明的是,实际中并不一定是过期了就立即更新sid,网站可能会需要你重新登陆才会生成新的sid,总之因为session并没有一套固定的标准,其实现是非常灵活的。
以淘宝为例,登录前:
可以看到登录前www.taobao.com下是没有token的,仅有6个cookie(并不知道是干嘛的)。
跳转到登陆页面,淘宝的登陆页面有很多隐藏的input,我试着把它们显示出来,填入一些值,提交,似乎然并卵,一样能正常登陆:
这里响应了一个_tb_token_
,这个应该就是“口令”了,口令的路径是根路径,也就是说访问淘宝子路径的页面也能访问到这个token。我们再来看看登陆之后的样子:
登陆之后新增了很多cookie,其中有一些以uc命名,难道是UC浏览器同步过来的(乱猜的··)?
3. 基于查询字符串的实现
通过查询字符串使用需要把sessionID设置到url中,风险比较大(别人用你的url访问就可以拥有你的权限),实际使用较少,可以作为cookie方案的降级。此外书中还提到了可以用ETag来存储sid,这种方式比修改url更安全。
4. session与内存和安全
以上的实现是直接把session存放在对象中,然而当用户量巨大时,这种方式会拖慢node的运行(因为node的垃圾回收机制),可以选择使用Redis这类的高速缓存来解决内存的问题。
Redis是一个开源的使用ANSIC语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
session的安全性较高,但是也不是完全没有漏洞的,一旦cookie被劫持,用户的身份还是有可能被冒用的,具体见link。